From be0542b1939de6a85ebf3e0e5ffd3c7c1429d0a8 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 3 Oct 2025 17:48:30 -0400 Subject: [PATCH 001/255] Add CodeQL suppresion for NativeCommandProcessor (#26145) --- .../engine/NativeCommandProcessor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 11aaff1535f..6b27c4542ab 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -1677,6 +1677,7 @@ private ProcessStartInfo GetProcessStartInfo( { using (ParameterBinderBase.bindingTracer.TraceScope("BIND argument [{0}]", NativeParameterBinderController.Arguments)) { + // codeql[cs/microsoft/command-line-injection ] - This is intended PowerShell behavior as NativeParameterBinderController.Arguments is what the native parameter binder generates based on the user input when invoking the command and cannot be injected externally. startInfo.Arguments = NativeParameterBinderController.Arguments; } } From b5279a25a1ea4fced17cf58244ae8a294a80dc35 Mon Sep 17 00:00:00 2001 From: Jonathan Gilbert Date: Sun, 5 Oct 2025 23:48:42 -0500 Subject: [PATCH 002/255] Correct handling of explicit -Empty:$false parameter value in New-Guid (#26140) --- .../commands/utility/NewGuidCommand.cs | 2 +- .../Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 | 5 +++++ 2 files changed, 6 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 3aa070bcd62..c384b2a2578 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 = ParameterSetName is "Empty" ? Guid.Empty : Guid.NewGuid(); + guid = Empty.ToBool() ? Guid.Empty : Guid.NewGuid(); } 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 54d609d2753..8756fed7726 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 @@ -17,6 +17,11 @@ Describe "New-Guid" -Tags "CI" { $guid.ToString() | Should -BeExactly "00000000-0000-0000-0000-000000000000" } + It "Should respect explicit false value for -Empty" { + $guid = New-Guid -Empty:$false + $guid.ToString() | Should -Not -BeExactly "00000000-0000-0000-0000-000000000000" + } + It "Should convert a string to a guid" { $guid1 = New-Guid $guid2 = New-Guid -InputObject $guid1.ToString() From 60d4bc39bc5c2b8d1b7db41d86c06272788740ba Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Tue, 7 Oct 2025 10:28:19 +0100 Subject: [PATCH 003/255] Fix memory leak in `GetFileShares` (#25896) --- .../CommandCompletion/CompletionCompleters.cs | 58 +++++++++++-------- .../Interop/Windows/NetApiBufferFree.cs | 16 +++++ 2 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 8ca133cf1c2..2b90bc02837 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -5204,38 +5204,48 @@ internal static List GetFileShares(string machine, bool ignoreHidden) uint numEntries = 0; uint totalEntries; uint resumeHandle = 0; - int result = Interop.Windows.NetShareEnum( - machine, - level: 1, - out shBuf, - Interop.Windows.MAX_PREFERRED_LENGTH, - out numEntries, - out totalEntries, - ref resumeHandle); - - var shares = new List(); - if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) + try { - for (int i = 0; i < numEntries; ++i) - { - nint curInfoPtr = shBuf + (Marshal.SizeOf() * i); - SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); + int result = Interop.Windows.NetShareEnum( + machine, + level: 1, + out shBuf, + Interop.Windows.MAX_PREFERRED_LENGTH, + out numEntries, + out totalEntries, + ref resumeHandle); - if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + var shares = new List(); + if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) + { + for (int i = 0; i < numEntries; ++i) { - continue; - } + nint curInfoPtr = shBuf + (Marshal.SizeOf() * i); + SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); - if (ignoreHidden && shareInfo.netname.EndsWith('$')) - { - continue; + if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + { + continue; + } + + if (ignoreHidden && shareInfo.netname.EndsWith('$')) + { + continue; + } + + shares.Add(shareInfo.netname); } + } - shares.Add(shareInfo.netname); + return shares; + } + finally + { + if (shBuf != nint.Zero) + { + Interop.Windows.NetApiBufferFree(shBuf); } } - - return shares; #endif } diff --git a/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs b/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs new file mode 100644 index 00000000000..4f7d0541872 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + + [LibraryImport("Netapi32.dll")] + internal static partial uint NetApiBufferFree(nint Buffer); + } +} From 0ef738b36f71bc1cfacd9f5149e6af325153302b Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 8 Oct 2025 08:34:15 +1000 Subject: [PATCH 004/255] Add property and event for debug attach (#25788) Adds the `IsRemoteDebuggerAttached` property to `Runspace` that indicates whether a debugger is attached to the Runspace through the `Debug-Runspace` cmdlet. Also adds a new engine event `PowerShell.OnDebugAttach` that is emitted when the debugger is attached. --- .../commands/utility/DebugRunspaceCommand.cs | 10 +++++++ .../engine/EventManager.cs | 7 ++++- .../engine/hostifaces/Connection.cs | 6 +++++ .../Debug-Runspace.Tests.ps1 | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index f5d84dfd195..756afff13de 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -265,6 +265,15 @@ private void WaitAndReceiveRunspaceOutput() // Set up host script debugger to debug the runspace. _debugger.DebugRunspace(_runspace, breakAll: BreakAll); + _runspace.IsRemoteDebuggerAttached = true; + _runspace.Events?.GenerateEvent( + PSEngineEvent.OnDebugAttach, + sender: null, + args: Array.Empty(), + extraData: null, + processInCurrentThread: true, + waitForCompletionInCurrentThread: false); + while (_debugging) { // Wait for running script. @@ -307,6 +316,7 @@ private void WaitAndReceiveRunspaceOutput() { _runspace.AvailabilityChanged -= HandleRunspaceAvailabilityChanged; _debugger.NestedDebuggingCancelledEvent -= HandleDebuggerNestedDebuggingCancelledEvent; + _runspace.IsRemoteDebuggerAttached = false; _debugger.StopDebugRunspace(_runspace); _newRunningScriptEvent.Dispose(); } diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index 75538135b92..e72a34cef1c 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -1830,6 +1830,11 @@ private PSEngineEvent() { } /// public const string OnIdle = "PowerShell.OnIdle"; + /// + /// Called when Debug-Runspace has attached a debugger to the current runspace. + /// + public const string OnDebugAttach = "PowerShell.OnDebugAttach"; + /// /// Called during scriptblock invocation. /// @@ -1843,7 +1848,7 @@ private PSEngineEvent() { } /// /// A HashSet that contains all engine event names. /// - internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke }; + internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnDebugAttach, OnScriptBlockInvoke }; } /// diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index 3f5911d1c62..ccb918c7dd9 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -761,6 +761,12 @@ public string Name /// Gets the Runspace Id. /// public int Id { get; } + + /// + /// Gets and sets a boolean indicating whether the runspace has a + /// debugger attached with Debug-Runspace. + /// + public bool IsRemoteDebuggerAttached { get; internal set; } /// /// Returns protocol version that the remote server uses for PS remoting. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 index 8aa28b00aef..2b517e4f301 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 @@ -31,6 +31,33 @@ Describe "Debug-Runspace" -Tag "CI" { $rs1.Debugger.SetDebugMode("None") { Debug-Runspace -Runspace $rs1 -ErrorAction stop } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.DebugRunspaceCommand" } + + It "Should write attach event and mark runspace as having a remote debugger attached" { + $onAttachName = [System.Management.Automation.PSEngineEvent]::OnDebugAttach + + $debugTarget = [PowerShell]::Create() + $null = $debugTarget.AddCommand('Wait-Event').AddParameter('SourceIdentifier', $onAttachName) + $waitTask = $debugTarget.BeginInvoke() + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeFalse + + $debugger = [PowerShell]::Create() + $null = $debugger.AddCommand('Debug-Runspace').AddParameter('Id', $debugTarget.Runspace.Id) + $debugTask = $debugger.BeginInvoke() + + $waitTask.AsyncWaitHandle.WaitOne(5000) | Should -BeTrue + $waitInfo = $debugTarget.EndInvoke($waitTask) + $waitInfo.SourceIdentifier | Should -Be $onAttachName + + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeTrue + + $debugger.Stop() + $exp = { + $debugger.EndInvoke($debugTask) + } | Should -Throw -PassThru + $exp.FullyQualifiedErrorId | Should -Be "PipelineStoppedException" + + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeFalse + } } From 5cdafbc4c7840dedfd5c9490ddb2e0619d0a2a23 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Wed, 8 Oct 2025 04:22:54 +0100 Subject: [PATCH 005/255] Enable CA1858: Use 'StartsWith' instead of 'IndexOf' (#26107) --- .globalconfig | 4 ++++ .../security/SecureStringCommands.cs | 3 +-- .../namespaces/FileSystemProvider.cs | 2 +- .../namespaces/LocationGlobber.cs | 4 ++-- src/System.Management.Automation/security/Authenticode.cs | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.globalconfig b/.globalconfig index fe6b1a03a95..6929917b659 100644 --- a/.globalconfig +++ b/.globalconfig @@ -510,6 +510,10 @@ dotnet_diagnostic.CA1846.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847 dotnet_diagnostic.CA1847.severity = warning +# CA1858: Use 'StartsWith' instead of 'IndexOf' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858 +dotnet_diagnostic.CA1858.severity = warning + # CA1868: Unnecessary call to 'Contains' for sets # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning diff --git a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs index c727476c0a3..29179a7046d 100644 --- a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs @@ -311,8 +311,7 @@ protected override void ProcessRecord() byte[] iv = null; // If this is a V2 package - if (String.IndexOf(SecureStringHelper.SecureStringExportHeader, - StringComparison.OrdinalIgnoreCase) == 0) + if (String.StartsWith(SecureStringHelper.SecureStringExportHeader, StringComparison.OrdinalIgnoreCase)) { try { diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index c5df7a9339a..56c8a074334 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -8286,7 +8286,7 @@ internal static void DeleteFileStream(string path, string streamName) ArgumentNullException.ThrowIfNull(streamName); string adjustedStreamName = streamName.Trim(); - if (adjustedStreamName.IndexOf(':') != 0) + if (!adjustedStreamName.StartsWith(':')) { adjustedStreamName = ":" + adjustedStreamName; } diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index 19377985c81..65c28f1586d 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -4539,7 +4539,7 @@ internal static bool IsHomePath(string path) } } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Support the single "~" if (path.Length == 1) @@ -4638,7 +4638,7 @@ internal string GetHomeRelativePath(string path) } } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Strip of the ~ and the \ or / if present diff --git a/src/System.Management.Automation/security/Authenticode.cs b/src/System.Management.Automation/security/Authenticode.cs index 591f64d6298..3a7720616d1 100644 --- a/src/System.Management.Automation/security/Authenticode.cs +++ b/src/System.Management.Automation/security/Authenticode.cs @@ -115,8 +115,8 @@ internal static Signature SignFile(SigningOption option, if (!string.IsNullOrEmpty(timeStampServerUrl)) { if ((timeStampServerUrl.Length <= 7) || ( - (timeStampServerUrl.IndexOf("http://", StringComparison.OrdinalIgnoreCase) != 0) && - (timeStampServerUrl.IndexOf("https://", StringComparison.OrdinalIgnoreCase) != 0))) + !timeStampServerUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && + !timeStampServerUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { throw PSTraceSource.NewArgumentException( nameof(certificate), From db77d2ede54e54f0bca8a59d88a0ba217156b028 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Wed, 8 Oct 2025 05:10:48 +0100 Subject: [PATCH 006/255] Enable CA1860: Avoid using 'Enumerable.Any()' extension method (#26109) --- .globalconfig | 4 ++++ src/System.Management.Automation/engine/MshObject.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index 6929917b659..c0035a9bfd8 100644 --- a/.globalconfig +++ b/.globalconfig @@ -514,6 +514,10 @@ dotnet_diagnostic.CA1847.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858 dotnet_diagnostic.CA1858.severity = warning +# CA1860: Avoid using 'Enumerable.Any()' extension method +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860 +dotnet_diagnostic.CA1860.severity = warning + # CA1868: Unnecessary call to 'Contains' for sets # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index 8303058759d..d788a8a3b43 100644 --- a/src/System.Management.Automation/engine/MshObject.cs +++ b/src/System.Management.Automation/engine/MshObject.cs @@ -1611,7 +1611,7 @@ public virtual PSObject Copy() bool needToReAddInstanceMembersAndTypeNames = !object.ReferenceEquals(GetKeyForResurrectionTables(this), GetKeyForResurrectionTables(returnValue)); if (needToReAddInstanceMembersAndTypeNames) { - Diagnostics.Assert(!returnValue.InstanceMembers.Any(), "needToReAddInstanceMembersAndTypeNames should mean that the new object has a fresh/empty list of instance members"); + Diagnostics.Assert(returnValue.InstanceMembers.Count == 0, "needToReAddInstanceMembersAndTypeNames should mean that the new object has a fresh/empty list of instance members"); foreach (PSMemberInfo member in this.InstanceMembers) { if (member.IsHidden) From 571537c34fc16b08dc85213fb73eee8a90925f33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:40:01 -0700 Subject: [PATCH 007/255] Bump github/codeql-action from 3.30.3 to 4.30.7 (#26159) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linux-ci.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 30f5acd5619..5c2118ef7be 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -192,7 +192,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -218,7 +218,7 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v3.29.5 ready_to_merge: name: Linux ready to merge diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 237a05d3479..a9879816135 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # v3.29.5 with: sarif_file: results.sarif From f247d802dd89495d786994a47eb2ee8f852bc97d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:03:36 +0000 Subject: [PATCH 008/255] Bump ossf/scorecard-action from 2.4.2 to 2.4.3 (#26128) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a9879816135..a168f12319f 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif From d581baabd4e70c89e0370fb95d0f81aefbf06463 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:41:08 +0100 Subject: [PATCH 009/255] Check for `GetWindowPlacement` success (#26122) --- test/powershell/Host/ConsoleHost.Tests.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index fffd959654b..ee501a83b63 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -985,7 +985,12 @@ public static WINDOWPLACEMENT GetPlacement(IntPtr hwnd) { WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); placement.length = Marshal.SizeOf(placement); - GetWindowPlacement(hwnd, ref placement); + + if (!GetWindowPlacement(hwnd, ref placement)) + { + throw new System.ComponentModel.Win32Exception(); + } + return placement; } From e562d3f39a84f5f6c0350a956db4f09bde347040 Mon Sep 17 00:00:00 2001 From: Jonathan Gilbert Date: Thu, 9 Oct 2025 05:48:52 -0500 Subject: [PATCH 010/255] Correct handling of explicit -Since:$false parameter value in Get-Uptime (#26141) --- .../commands/utility/GetUptime.cs | 17 ++++++++--------- .../Get-Uptime.Tests.ps1 | 4 ++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs index e8dcfbe254a..c21165301e2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs @@ -36,16 +36,15 @@ protected override void ProcessRecord() { TimeSpan uptime = TimeSpan.FromSeconds(Stopwatch.GetTimestamp() / Stopwatch.Frequency); - switch (ParameterSetName) + if (Since) { - case TimespanParameterSet: - // return TimeSpan of time since the system started up - WriteObject(uptime); - break; - case SinceParameterSet: - // return Datetime when the system started up - WriteObject(DateTime.Now.Subtract(uptime)); - break; + // Output the time of the last system boot. + WriteObject(DateTime.Now.Subtract(uptime)); + } + else + { + // Output the time elapsed since the last system boot. + WriteObject(uptime); } } else diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 index aff5e4d50af..b3275a61f2a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 @@ -25,6 +25,10 @@ Describe "Get-Uptime" -Tags "CI" { $upt = Get-Uptime -Since $upt | Should -BeOfType DateTime } + It "Get-Uptime -Since:`$false return TimeSpan" { + $upt = Get-Uptime -Since:$false + $upt | Should -BeOfType TimeSpan + } It "Get-Uptime throw if IsHighResolution == false" { # Enable the test hook [system.management.automation.internal.internaltesthooks]::SetTestHook('StopwatchIsNotHighResolution', $true) From a842b2ba11f7e17be738709c982f5e794070886a Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:34:49 +0100 Subject: [PATCH 011/255] Enable CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' (#26106) --- .globalconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.globalconfig b/.globalconfig index c0035a9bfd8..a0501d4cfa4 100644 --- a/.globalconfig +++ b/.globalconfig @@ -510,6 +510,10 @@ dotnet_diagnostic.CA1846.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847 dotnet_diagnostic.CA1847.severity = warning +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1853 +dotnet_diagnostic.CA1853.severity = warning + # CA1858: Use 'StartsWith' instead of 'IndexOf' # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858 dotnet_diagnostic.CA1858.severity = warning From cad2949d744eaf0f6618cf3d58b2c3c90bbcbd8c Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 9 Oct 2025 16:48:32 -0700 Subject: [PATCH 012/255] Update `metadata.json` for v7.6.0-preview.5 release (#26158) --- tools/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/metadata.json b/tools/metadata.json index 377b6ead763..f9339e276f2 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,10 +1,10 @@ { "StableReleaseTag": "v7.5.3", - "PreviewReleaseTag": "v7.6.0-preview.4", + "PreviewReleaseTag": "v7.6.0-preview.5", "ServicingReleaseTag": "v7.0.13", "ReleaseTag": "v7.5.3", "LTSReleaseTag" : ["v7.4.12"], - "NextReleaseTag": "v7.6.0-preview.5", + "NextReleaseTag": "v7.6.0-preview.6", "LTSRelease": { "Latest": false, "Package": false }, "StableRelease": { "Latest": false, "Package": false } } From 3207a744b5c86ffcfe68af201be30d449bfe1f34 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 10 Oct 2025 05:45:47 +0100 Subject: [PATCH 013/255] Disable IDE0049: PreferBuiltInOrFrameworkType (#26094) --- .globalconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index a0501d4cfa4..7e345757abf 100644 --- a/.globalconfig +++ b/.globalconfig @@ -1342,7 +1342,7 @@ dotnet_diagnostic.IDE0048.severity = suggestion # IDE0049: PreferBuiltInOrFrameworkType # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0049 -dotnet_diagnostic.IDE0049.severity = warning +dotnet_diagnostic.IDE0049.severity = suggestion # IDE0050: ConvertAnonymousTypeToTuple # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0050 From 38f7f937d27ec773376c64c0748ffbcb1133204f Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 10 Oct 2025 08:02:43 +0100 Subject: [PATCH 014/255] Enable `SA1206`: Declaration keywords should follow order (#24973) --- .globalconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index 7e345757abf..b125f70e254 100644 --- a/.globalconfig +++ b/.globalconfig @@ -1850,7 +1850,7 @@ dotnet_diagnostic.SA1205.severity = warning # SA1206: Declaration keywords should follow order # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1206.md -dotnet_diagnostic.SA1206.severity = none +dotnet_diagnostic.SA1206.severity = warning # SA1207: Protected should come before internal # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1207.md From 659c73ffd30d8309910cbd4b809f2238fe2603e4 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Sun, 12 Oct 2025 11:57:10 +0100 Subject: [PATCH 015/255] Avoid possible multiple enumerations in ImportModuleCommand.IsPs1xmlFileHelper_IsPresentInEntries (#26104) --- .../engine/Modules/ImportModuleCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 15e8bfe6a9a..05357bbb5e5 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -1371,7 +1371,7 @@ private void ImportModule_RemotelyViaCimSession( } } - private bool IsPs1xmlFileHelper_IsPresentInEntries(RemoteDiscoveryHelper.CimModuleFile cimModuleFile, IEnumerable manifestEntries) + private bool IsPs1xmlFileHelper_IsPresentInEntries(RemoteDiscoveryHelper.CimModuleFile cimModuleFile, List manifestEntries) { const string ps1xmlExt = ".ps1xml"; string fileName = cimModuleFile.FileName; From 26bb188c8be0cda6cb548ce1a12840ebf67e1331 Mon Sep 17 00:00:00 2001 From: Jorge Suarez Date: Sun, 12 Oct 2025 23:55:20 -0700 Subject: [PATCH 016/255] Improve ValidateLength error message consistency and refactor validation tests (#25806) --- .../resources/Metadata.resx | 2 +- .../engine/Basic/ValidateAttributes.Tests.ps1 | 182 ++++++++++++------ 2 files changed, 124 insertions(+), 60 deletions(-) diff --git a/src/System.Management.Automation/resources/Metadata.resx b/src/System.Management.Automation/resources/Metadata.resx index de0a30f77e2..b8dc3ea4eb4 100644 --- a/src/System.Management.Automation/resources/Metadata.resx +++ b/src/System.Management.Automation/resources/Metadata.resx @@ -175,7 +175,7 @@ The character length ({1}) of the argument is too short. Specify an argument with a length that is greater than or equal to "{0}", and then try the command again. - The character length of the {1} argument is too long. Shorten the character length of the argument so it is fewer than or equal to "{0}" characters, and then try the command again. + The character length ({1}) of the argument is too long. Specify an argument with a length that is shorter than or equal to "{0}", and then try the command again. The argument "{0}" does not belong to the set "{1}" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. diff --git a/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 b/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 index c8d56786d51..ff4ad0ebcac 100644 --- a/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 +++ b/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 @@ -6,33 +6,33 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateCount(-1,2)] [string[]] $bar) }; foo } + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(-1,2)] [string[]] $Items) }; Test-ArrayCount } FullyQualifiedErrorId = "ExceptionConstructingAttribute" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateCount(1,-1)] [string[]] $bar) }; foo } + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(1,-1)] [string[]] $Items) }; Test-ArrayCount } FullyQualifiedErrorId = "ExceptionConstructingAttribute" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 1)] [string[]] $bar) }; foo } + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 1)] [string[]] $Items) }; Test-ArrayCount } FullyQualifiedErrorId = "ValidateRangeMaxLengthSmallerThanMinLength" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 2)] [string[]] $bar) }; foo 1 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 2)] [string[]] $Items) }; Test-ArrayCount 1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-ArrayCount" InnerErrorId = "ValidateCountExactFailure" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 3)] [string[]] $Items) }; Test-ArrayCount 1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-ArrayCount" InnerErrorId = "ValidateCountMinMaxFailure" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1,2,3,4 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 3)] [string[]] $Items) }; Test-ArrayCount 1,2,3,4 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-ArrayCount" InnerErrorId = "ValidateCountMinMaxFailure" } ) @@ -41,14 +41,14 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: :' -TestCases $testCases { param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } It 'No Exception: valid argument count' { - { function foo { param([ValidateCount(2, 4)] [string[]] $bar) }; foo 1,2,3,4 } | Should -Not -Throw + { function Test-ArrayCount { param([ValidateCount(2, 4)] [string[]] $Items) }; Test-ArrayCount 1,2,3,4 } | Should -Not -Throw } } @@ -56,22 +56,22 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange('xPositive')] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('xPositive')] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "ExceptionConstructingAttribute" InnerErrorId = "SubstringDisambiguationEnumParseThrewAnException" } @{ - ScriptBlock = { function foo { param([ValidateRange(2,1)] [int] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange(2,1)] [int] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "MaxRangeSmallerThanMinRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange("one",10)] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("one",10)] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "MinRangeNotTheSameTypeOfMaxRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange(1,"two")] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,"two")] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "MinRangeNotTheSameTypeOfMaxRange" InnerErrorId = "" } @@ -81,9 +81,9 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: :' -TestCases $testCases { param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } } @@ -91,25 +91,25 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo -1 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] [int] $Number) }; Test-NumericRange -1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeTooSmall" } @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo 11 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] [int] $Number) }; Test-NumericRange 11 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeTooBig" } @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] $bar) }; foo "one" } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] $Number) }; Test-NumericRange "one" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidationRangeElementType" } ) $validTestCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo 5 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] [int] $Number) }; Test-NumericRange 5 } } ) } @@ -117,9 +117,9 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: :' -TestCases $testCases { param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } @@ -133,115 +133,115 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo -1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [int] $Number) }; Test-NumericRange -1 } RangeType = "Positive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangePositiveFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [int] $Number) }; Test-NumericRange 0 } RangeType = "Positive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangePositiveFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] $Number) }; Test-NumericRange "one" } RangeType = "Positive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo -1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [int] $Number) }; Test-NumericRange -1 } RangeType = "NonNegative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNonNegativeFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] $Number) }; Test-NumericRange "one" } RangeType = "NonNegative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo 1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [int] $Number) }; Test-NumericRange 1 } RangeType = "Negative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNegativeFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [int] $Number) }; Test-NumericRange 0 } RangeType = "Negative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNegativeFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] $Number) }; Test-NumericRange "one" } RangeType = "Negative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] $bar) }; foo 1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] $Number) }; Test-NumericRange 1 } RangeType = "NonPositive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNonPositiveFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] $Number) }; Test-NumericRange "one" } RangeType = "NonPositive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } ) $validTestCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo 15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [int] $Number) }; Test-NumericRange 15 } RangeType = "Positive" TestValue = 15 } @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [double]$bar) }; foo ([double]::MaxValue) }; + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [double]$Number) }; Test-NumericRange ([double]::MaxValue) }; RangeType = "Positive" TestValue = [double]::MaxValue } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [int] $Number) }; Test-NumericRange 0 } RangeType = "NonNegative" TestValue = 0 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo 15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [int] $Number) }; Test-NumericRange 15 } RangeType = "NonNegative" TestValue = 15 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [double]$bar) }; foo ([double]::MaxValue) }; + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [double]$Number) }; Test-NumericRange ([double]::MaxValue) }; RangeType = "NonNegative" TestValue = [double]::MaxValue } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo -15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [int] $Number) }; Test-NumericRange -15 } RangeType = "Negative" TestValue = -15 } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [double]$bar) }; foo ([double]::MinValue) }; + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [double]$Number) }; Test-NumericRange ([double]::MinValue) }; TestValue = [double]::MinValue RangeType = "Negative" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] [int] $Number) }; Test-NumericRange 0 } RangeType = "NonPositive" TestValue = 0 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [int] $bar) }; foo -15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] [int] $Number) }; Test-NumericRange -15 } RangeType = "NonPositive" TestValue = -15 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [double]$bar) }; foo ([double]::MinValue) } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] [double]$Number) }; Test-NumericRange ([double]::MinValue) } RangeType = "NonPositive" TestValue = [double]::MinValue } @@ -251,9 +251,9 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: :, RangeType: ' -TestCases $testCases { param($ScriptBlock, $RangeType, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } @@ -263,6 +263,70 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { } } + Context "ValidateLength" { + BeforeAll { + $testCases = @( + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "a" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-StringLength" + InnerErrorId = "ValidateLengthMinLengthFailure" + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "abcdef" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-StringLength" + InnerErrorId = "ValidateLengthMaxLengthFailure" + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] $InputString) }; Test-StringLength 123 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-StringLength" + InnerErrorId = "ValidateLengthNotString" + } + ) + + $validTestCases = @( + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "abc" } + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "ab" } + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "abcde" } + } + ) + } + + It 'Exception: :' -TestCases $testCases { + param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) + + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru + if ($InnerErrorId) { + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + } + } + + It 'No Exception: valid string length' -TestCases $validTestCases { + param($ScriptBlock) + $ScriptBlock | Should -Not -Throw + } + + It 'ValidateLength error message should be properly formatted' { + function Test-ValidateLengthMax { param([ValidateLength(0,2)] [string] $Value) $Value } + function Test-ValidateLengthMin { param([ValidateLength(5,10)] [string] $Value) $Value } + + $TestStringTooLong = "11111" + $TestStringTooShort = "123" + $ExpectedMaxLength = 2 + $ExpectedMinLength = 5 + + $err = { Test-ValidateLengthMax $TestStringTooLong } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMax" -PassThru + $err.Exception.InnerException.Message | Should -Match ".+\($($TestStringTooLong.Length)\).+\`"$ExpectedMaxLength\`"" + + $err = { Test-ValidateLengthMin $TestStringTooShort } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMin" -PassThru + $err.Exception.InnerException.Message | Should -Match ".+\($($TestStringTooShort.Length)\).+\`"$ExpectedMinLength\`"" + } + } + Context "ValidateNotNull, ValidateNotNullOrEmpty, ValidateNotNullOrWhiteSpace and Not-Null-Or-Empty check for Mandatory parameter" { BeforeAll { From 47e38beda56c0e7fafed4142e4c1c7bcf1a67d2a Mon Sep 17 00:00:00 2001 From: Ryan Yates Date: Wed, 15 Oct 2025 17:31:04 +0100 Subject: [PATCH 017/255] Properly Expand Aliases to their actual ResolvedCommand (#25763) --- .../engine/TypeTable_Types_Ps1Xml.cs | 10 ++-------- .../Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 | 10 ++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs index c1a134cdfbf..26e3621c240 100644 --- a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs +++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs @@ -565,9 +565,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) typeName, new PSScriptProperty( @"DisplayName", - GetScriptBlock(@"if ($this.Name.IndexOf('-') -lt 0) - { - if ($null -ne $this.ResolvedCommand) + GetScriptBlock(@"if ($null -ne $this.ResolvedCommand) { $this.Name + "" -> "" + $this.ResolvedCommand.Name } @@ -575,11 +573,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) { $this.Name + "" -> "" + $this.Definition } - } - else - { - $this.Name - }"), + "), setterScript: null, shouldCloneOnAccess: true), typeMembers, diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 index edccb912ca7..a5b0b039d00 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 @@ -155,6 +155,16 @@ Describe "Get-Alias DRT Unit Tests" -Tags "CI" { $returnObject[$i].Definition | Should -Be 'Get-Command' } } + + It "Get-Alias DisplayName should always show AliasName -> ResolvedCommand for all aliases" { + Set-Alias -Name Test-MyAlias -Value Get-Command -Force + Set-Alias -Name tma -Value Test-MyAlias -force + $aliases = Get-Alias Test-MyAlias, tma + $aliases | ForEach-Object { + $_.DisplayName | Should -Be "$($_.Name) -> Get-Command" + } + $aliases.Name.foreach{Remove-Item Alias:$_ -ErrorAction SilentlyContinue} + } } Describe "Get-Alias" -Tags "CI" { From b77c80ec2f9c7ac69e10cc77ad3be0dfb2d18256 Mon Sep 17 00:00:00 2001 From: Amrit Anand <64958314+amritanand-py@users.noreply.github.com> Date: Wed, 15 Oct 2025 22:06:24 +0530 Subject: [PATCH 018/255] Replace custom method with File.ReadAllText() in ScriptAnalysis.cs (#26060) --- .../engine/Modules/ScriptAnalysis.cs | 15 +-------------- src/System.Management.Automation/utils/PsUtils.cs | 2 +- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs index 7207a7292c0..892d7375387 100644 --- a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs +++ b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs @@ -40,7 +40,7 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) // So eat the invalid operation } - string scriptContent = ReadScript(path); + string scriptContent = File.ReadAllText(path, Encoding.Default); ParseError[] errors; var moduleAst = (new Parser()).Parse(path, scriptContent, null, out errors, ParseMode.ModuleAnalysis); @@ -89,19 +89,6 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) return result; } - internal static string ReadScript(string path) - { - using (FileStream readerStream = new FileStream(path, FileMode.Open, FileAccess.Read)) - { - Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = readerStream.SafeFileHandle; - - using (StreamReader scriptReader = new StreamReader(readerStream, Encoding.Default)) - { - return scriptReader.ReadToEnd(); - } - } - } - internal List DiscoveredExports { get; set; } internal Dictionary DiscoveredAliases { get; set; } diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index d404581bf1f..ff2d0ade5ea 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -340,7 +340,7 @@ internal static Hashtable EvaluatePowerShellDataFile( internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys) { - string dataFileContents = ScriptAnalysis.ReadScript(psDataFilePath); + string dataFileContents = File.ReadAllText(psDataFilePath, Encoding.Default); ParseError[] parseErrors; var ast = (new Parser()).Parse(psDataFilePath, dataFileContents, null, out parseErrors, ParseMode.ModuleAnalysis); if (parseErrors.Length > 0) From 9c5ac7d6223a0c96e3b0b866bfa623f656e0c679 Mon Sep 17 00:00:00 2001 From: MartinGC94 <42123497+MartinGC94@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:50:37 +0200 Subject: [PATCH 019/255] Add Delimiter parameter to Get-Clipboard (#26134) --- .../management/GetClipboardCommand.cs | 42 ++++++++++++++++++- .../Clipboard.Tests.ps1 | 7 ++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs index dd878bd72ce..a2b7f03ce70 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs @@ -2,8 +2,10 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Management.Automation; +using System.Management.Automation.Language; using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands @@ -34,6 +36,13 @@ public SwitchParameter Raw } } + /// + /// Gets or sets the delimiters to use when splitting the clipboard content. + /// + [Parameter] + [ArgumentCompleter(typeof(DelimiterCompleter))] + public string[] Delimiter { get; set; } = [Environment.NewLine]; + private bool _raw; /// @@ -68,11 +77,40 @@ private List GetClipboardContentAsText() } else { - string[] splitSymbol = { Environment.NewLine }; - result.AddRange(textContent.Split(splitSymbol, StringSplitOptions.None)); + result.AddRange(textContent.Split(Delimiter, StringSplitOptions.None)); } return result; } } + + /// + /// Provides argument completion for the Delimiter parameter. + /// + public sealed class DelimiterCompleter : IArgumentCompleter + { + /// + /// Provides argument completion for the Delimiter parameter. + /// + /// The name of the command that is being completed. + /// The name of the parameter that is being completed. + /// The input text to filter the results by. + /// The ast of the command that triggered the completion. + /// The parameters bound to the command. + /// Completion results. + public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + { + wordToComplete ??= string.Empty; + var pattern = new WildcardPattern(wordToComplete + '*', WildcardOptions.IgnoreCase); + if (pattern.IsMatch("CRLF") || pattern.IsMatch("Windows")) + { + yield return new CompletionResult("\"`r`n\"", "CRLF", CompletionResultType.ParameterValue, "Windows (CRLF)"); + } + + if (pattern.IsMatch("LF") || pattern.IsMatch("Unix") || pattern.IsMatch("Linux")) + { + yield return new CompletionResult("\"`n\"", "LF", CompletionResultType.ParameterValue, "UNIX (LF)"); + } + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 index 7cd4f492bfa..62f8d77a44c 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 @@ -36,6 +36,13 @@ Describe 'Clipboard cmdlet tests' -Tag CI { Get-Clipboard -Raw | Should -BeExactly "1$([Environment]::NewLine)2" } + It 'Get-Clipboard -Delimiter should return items based on the delimiter' { + Set-Clipboard -Value "Line1`r`nLine2`nLine3" + $result = Get-Clipboard -Delimiter "`r`n", "`n" + $result.Count | Should -Be 3 + $result | ForEach-Object -Process {$_.Length | Should -Be "LineX".Length} + } + It 'Set-Clipboard -Append will add text' { 'hello' | Set-Clipboard 'world' | Set-Clipboard -Append From 0e90b57e63a6c58e0288cdd26ca58356ade57966 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 15 Oct 2025 12:33:31 -0700 Subject: [PATCH 020/255] Github Workflow cleanup (#26193) --- .github/workflows/AssignPrs.yml | 30 --------------- .github/workflows/createReminders.yml | 21 ---------- .github/workflows/markdownLink.yml | 51 ------------------------- .github/workflows/markdownLinkDaily.yml | 33 ---------------- .github/workflows/processReminders.yml | 21 ---------- tools/download.sh | 4 +- tools/install-powershell.sh | 6 ++- 7 files changed, 7 insertions(+), 159 deletions(-) delete mode 100644 .github/workflows/AssignPrs.yml delete mode 100644 .github/workflows/createReminders.yml delete mode 100644 .github/workflows/markdownLink.yml delete mode 100644 .github/workflows/markdownLinkDaily.yml delete mode 100644 .github/workflows/processReminders.yml diff --git a/.github/workflows/AssignPrs.yml b/.github/workflows/AssignPrs.yml deleted file mode 100644 index a01c0bb0950..00000000000 --- a/.github/workflows/AssignPrs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Auto Assign PR Maintainer -on: - issues: - types: [opened, edited] -permissions: - contents: read - -jobs: - run: - if: github.repository_owner == 'PowerShell' - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: wow-actions/auto-assign@67fafa03df61d7e5f201734a2fa60d1ab111880d # v3.0.2 - if: github.event.issue.pull_request - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # using the `org/team_slug` or `/team_slug` syntax to add git team as reviewers - assignees: | - TravisEz13 - daxian-dbw - adityapatwardhan - iSazonov - SeeminglyScience - skipDraft: true - skipKeywords: wip, draft - addReviewers: false - numberOfAssignees: 1 diff --git a/.github/workflows/createReminders.yml b/.github/workflows/createReminders.yml deleted file mode 100644 index bab5342fe0b..00000000000 --- a/.github/workflows/createReminders.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'Create reminder' - -on: - issue_comment: - types: [created, edited] - -permissions: - contents: read - -jobs: - reminder: - if: github.repository_owner == 'PowerShell' - - permissions: - issues: write # for agrc/create-reminder-action to set reminders on issues - pull-requests: write # for agrc/create-reminder-action to set reminders on PRs - runs-on: ubuntu-latest - - steps: - - name: check for reminder - uses: agrc/create-reminder-action@30624e347adbc7ff2dd287ad0632499552e048e8 # v1.1.22 diff --git a/.github/workflows/markdownLink.yml b/.github/workflows/markdownLink.yml deleted file mode 100644 index 5ad78cf84d4..00000000000 --- a/.github/workflows/markdownLink.yml +++ /dev/null @@ -1,51 +0,0 @@ -on: - pull_request: - branches: - - master - -name: Check modified markdown files -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - if: github.repository_owner == 'PowerShell' - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - check-modified-files-only: 'yes' - config-file: .github/workflows/markdown-link/config.json - markdown-lint: - permissions: - contents: read - packages: read - statuses: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - # Full git history is needed to get a proper - # list of changed files within `super-linter` - fetch-depth: 0 - - name: Load super-linter configuration - # Use grep inverse matching to exclude eventual comments in the .env file - # because the GitHub Actions command to set environment variables doesn't - # support comments. - # Ref: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-environment-variable - run: grep -v '^#' tools/super-linter/config/super-linter.env >> "$GITHUB_ENV" - - name: Lint Markdown - uses: super-linter/super-linter@5119dcd8011e92182ce8219d9e9efc82f16fddb6 # v8.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Super-Linter correction instructions - if: failure() - uses: actions/github-script@v8 - with: - script: | - const message = "Super-Linter found issues in the changed files. Please check the logs for details. You can run the linter locally using the command: `./tools/super-lister/super-lister.ps1`."; - core.setFailed(message); diff --git a/.github/workflows/markdownLinkDaily.yml b/.github/workflows/markdownLinkDaily.yml deleted file mode 100644 index 7f5789ed96d..00000000000 --- a/.github/workflows/markdownLinkDaily.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -name: PowerShell Daily Markdown Link Verification - -on: - workflow_dispatch: - schedule: - # At 13:00 UTC every day. - - cron: '0 13 * * *' - -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - if: github.repository == 'PowerShell/PowerShell' - steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Check Links - uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - config-file: .github/workflows/markdown-link/config.json - - name: Microsoft Teams Notifier - uses: skitionek/notify-microsoft-teams@e7a2493ac87dad8aa7a62f079f295e54ff511d88 # master - if: failure() - with: - webhook_url: ${{ secrets.PS_BUILD_TEAMS_CHANNEL }} - overwrite: "{title: `Failure in .github/markdownLinkDaily.yml validating links. Look at ${workflow_link}`}" diff --git a/.github/workflows/processReminders.yml b/.github/workflows/processReminders.yml deleted file mode 100644 index 18de16159ac..00000000000 --- a/.github/workflows/processReminders.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'Process reminders' - -on: - schedule: - - cron: '*/15 * * * *' - workflow_dispatch: - -permissions: - contents: read - -jobs: - reminder: - if: github.repository_owner == 'PowerShell' - permissions: - issues: write # for agrc/reminder-action to set reminders on issues - pull-requests: write # for agrc/reminder-action to set reminders on PRs - runs-on: ubuntu-latest - - steps: - - name: check reminders and notify - uses: agrc/reminder-action@3095f64f8f0c26c751bee802cb1008ece5953078 # v1.0.18 diff --git a/tools/download.sh b/tools/download.sh index 6a6c6436b4b..f1e8c42cdc3 100644 --- a/tools/download.sh +++ b/tools/download.sh @@ -1 +1,3 @@ -bash <(curl -s https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.sh) +# Pin to specific commit for security (OpenSSF Scorecard requirement) +# Pinned commit: 26bb188c8 - "Improve ValidateLength error message consistency and refactor validation tests" (2025-10-12) +bash <(curl -s https://raw.githubusercontent.com/PowerShell/PowerShell/26bb188c8be0cda6cb548ce1a12840ebf67e1331/tools/install-powershell.sh) diff --git a/tools/install-powershell.sh b/tools/install-powershell.sh index 128f5664483..91425c183a8 100755 --- a/tools/install-powershell.sh +++ b/tools/install-powershell.sh @@ -26,7 +26,9 @@ install(){ #gitrepo paths are overrideable to run from your own fork or branch for testing or private distribution local VERSION="1.2.0" - local gitreposubpath="PowerShell/PowerShell/master" + # Pin to specific commit for security (OpenSSF Scorecard requirement) + # Pinned commit: 26bb188c8 - "Improve ValidateLength error message consistency and refactor validation tests" (2025-10-12) + local gitreposubpath="PowerShell/PowerShell/26bb188c8be0cda6cb548ce1a12840ebf67e1331" local gitreposcriptroot="https://raw.githubusercontent.com/$gitreposubpath/tools" local gitscriptname="install-powershell.psh" @@ -125,7 +127,7 @@ install(){ if [[ $osname = *SUSE* ]]; then DistroBasedOn='suse' REV=$(source /etc/os-release; echo $VERSION_ID) - fi + fi OS=$(lowercase $OS) DistroBasedOn=$(lowercase $DistroBasedOn) fi From de7fd463ba5fb60c649922a5a60daaa664fe8e4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:37:02 -0700 Subject: [PATCH 021/255] Bump github/codeql-action from 4.30.7 to 4.30.8 (#26184) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linux-ci.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 5c2118ef7be..b58b75c64a4 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -192,7 +192,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v3.29.5 + uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -218,7 +218,7 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v3.29.5 + uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 ready_to_merge: name: Linux ready to merge diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a168f12319f..2a12425a829 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # v3.29.5 + uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 with: sarif_file: results.sarif From 59a85e04afd16b6e11b2e6ab448eb4399f877767 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:35:42 -0700 Subject: [PATCH 022/255] Bump actions/dependency-review-action from 4.7.3 to 4.8.1 (#26183) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c0f2e6e0092..c3cc0624c45 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3 + uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 From 7e081b30b886a57c2ee47d95fdce8880142aa514 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 15 Oct 2025 14:06:00 -0700 Subject: [PATCH 023/255] Update vPack name (#26090) --- .pipelines/PowerShell-vPack-Official.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index f110ff0366a..3b151127cb1 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -11,8 +11,9 @@ parameters: # parameters are shown up in ADO UI in a build queue time - name: vPackName type: string displayName: 'VPack Name:' - default: 'PowerShell' + default: 'PowerShell.BuildTool' values: + - PowerShell.BuildTool - PowerShell - PowerShellDoNotUse - name: 'ReleaseTagVar' From e733fd7343f88e79534746393995201f79b47869 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 16 Oct 2025 05:17:47 +0100 Subject: [PATCH 024/255] Fix mismatched indentation in `.config/suppress.json` (#26192) Co-authored-by: Travis Plunk --- .config/suppress.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.config/suppress.json b/.config/suppress.json index 55e607b1b0c..9be220b291e 100644 --- a/.config/suppress.json +++ b/.config/suppress.json @@ -1,17 +1,17 @@ { - "tool": "Credential Scanner", - "suppressions": [ - { - "file": "\\test\\tools\\Modules\\WebListener\\ClientCert.pfx", - "_justification": "Test certificate with private key" - }, - { - "file": "\\test\\tools\\Modules\\WebListener\\ServerCert.pfx", - "_justification": "Test certificate with private key" - }, - { - "file": "\\test\\powershell\\Modules\\Microsoft.PowerShell.Security\\certificateCommon.psm1", - "_justification": "Test certificate with private key and inline suppression isn't working" - } - ] + "tool": "Credential Scanner", + "suppressions": [ + { + "file": "\\test\\tools\\Modules\\WebListener\\ClientCert.pfx", + "_justification": "Test certificate with private key" + }, + { + "file": "\\test\\tools\\Modules\\WebListener\\ServerCert.pfx", + "_justification": "Test certificate with private key" + }, + { + "file": "\\test\\powershell\\Modules\\Microsoft.PowerShell.Security\\certificateCommon.psm1", + "_justification": "Test certificate with private key and inline suppression isn't working" + } + ] } From 123b4504438597863a0381eae4fe9d86ee7d8f0b Mon Sep 17 00:00:00 2001 From: mikkas456 Date: Thu, 16 Oct 2025 07:09:49 +0200 Subject: [PATCH 025/255] Handle null reference exception in CsvCommands.cs: ConvertPSObjectToCSV (#26144) --- .../commands/utility/CsvCommands.cs | 2 +- .../ConvertTo-Csv.Tests.ps1 | 8 ++++++ .../Export-Csv.Tests.ps1 | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index 414b472e640..0ff52372864 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -1044,7 +1044,7 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN { if (dictionary.Contains(propertyName)) { - value = dictionary[propertyName].ToString(); + value = dictionary[propertyName]?.ToString(); } else if (mshObject.Properties[propertyName] is PSPropertyInfo property) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 index 0d484260813..298b8140468 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 @@ -212,6 +212,7 @@ Describe "ConvertTo-Csv" -Tags "CI" { [ordered]@{ Number = $_; Letter = $Letters[$_] } } $CsvString = $Items | ConvertTo-Csv + $TestHashTable = [ordered]@{ 'first' = 'value1'; 'second' = $null; 'third' = 'value3' } } It 'should treat dictionary entries as properties' { @@ -235,5 +236,12 @@ Describe "ConvertTo-Csv" -Tags "CI" { $NewCsvString[0] | Should -MatchExactly 'Extra' $NewCsvString | Select-Object -Skip 1 | Should -MatchExactly 'Surprise!' } + + It 'should properly convert hashtable with null and non-null values'{ + $CsvResult = $TestHashTable | ConvertTo-Csv + + $CsvResult[0] | Should -BeExactly "`"first`",`"second`",`"third`"" + $CsvResult[1] | Should -BeExactly "`"value1`",$null,`"value3`"" + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 index 67234f24c58..db5c6aff9e3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 @@ -11,6 +11,7 @@ Describe "Export-Csv" -Tags "CI" { $P1 = [pscustomobject]@{"P1" = "first"} $P2 = [pscustomobject]@{"P2" = "second"} $P11 = [pscustomobject]@{"P1" = "eleventh"} + $testHashTable = @{ 'first' = "value1"; 'second' = $null; 'third' = "value3" } } AfterEach { @@ -249,6 +250,33 @@ Describe "Export-Csv" -Tags "CI" { $contents[1].Contains($delimiter) | Should -BeTrue } + It "Should not throw when exporting hashtable with property that has null value"{ + { $testHashTable | Export-Csv -Path $testCsv } | Should -Not -Throw + } + + It "Should not throw when exporting PSCustomObject with property that has null value"{ + $testObject = [pscustomobject]$testHashTable + { $testObject | Export-Csv -Path $testCsv } | Should -Not -Throw + } + + It "Export hashtable with null and non-null values"{ + $testHashTable | Export-Csv -Path $testCsv + $result2 = Import-CSV -Path $testCsv + + $result2.first | Should -BeExactly "value1" + $result2.second | Should -BeNullOrEmpty + $result2.third | Should -BeExactly "value3" + } + + It "Export hashtable with non-null values"{ + $testTable = @{ 'first' = "value1"; 'second' = "value2" } + $testTable | Export-Csv -Path $testCsv + $results = Import-CSV -Path $testCsv + + $results.first | Should -BeExactly "value1" + $results.second | Should -BeExactly "value2" + } + Context "UseQuotes parameter" { # A minimum of tests. The rest are in ConvertTo-Csv.Tests.ps1 From 380758aff41407d1bfe481345b16d5075a27c471 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 16 Oct 2025 06:16:24 +0100 Subject: [PATCH 026/255] Enable IDE0019: InlineAsTypeCheck (#25920) --- .globalconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index b125f70e254..68fb0a66b2d 100644 --- a/.globalconfig +++ b/.globalconfig @@ -1222,7 +1222,7 @@ dotnet_diagnostic.IDE0018.severity = silent # IDE0019: InlineAsTypeCheck # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0019 -dotnet_diagnostic.IDE0019.severity = silent +dotnet_diagnostic.IDE0019.severity = warning # IDE0020: InlineIsTypeCheck # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0020 From d2d0defd5fe46eadc472eb3f4ea0655aef8e0dae Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 16 Oct 2025 06:23:32 +0100 Subject: [PATCH 027/255] Fix CA1852: Seal internal types. Part 1 (#26205) --- .../NewWinEventCommand.cs | 2 +- .../PdhHelper.cs | 2 +- .../management/GetComputerInfoCommand.cs | 26 +++++++++---------- .../commands/management/Process.cs | 4 +-- .../CurrentConfigurations.cs | 2 +- src/Microsoft.WSMan.Management/WsManHelper.cs | 4 +-- test/xUnit/csharp/test_AstDefaultVisit.cs | 6 ++--- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs index b0b102870c4..cdddba939f3 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs @@ -323,7 +323,7 @@ protected override void EndProcessing() } } - internal class EventWriteException : Exception + internal sealed class EventWriteException : Exception { internal EventWriteException(string msg, Exception innerException) : base(msg, innerException) diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs index b761aa6e30b..6f5d8f6e5ec 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs @@ -190,7 +190,7 @@ internal struct CounterHandleNInstance public string InstanceName; } - internal class PdhHelper : IDisposable + internal sealed class PdhHelper : IDisposable { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct PDH_COUNTER_PATH_ELEMENTS diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs index 3de7c723745..e355be6f3ff 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs @@ -1335,7 +1335,7 @@ protected static string GetLanguageName(uint? lcid) #pragma warning disable 649 // fields and properties in these class are assigned dynamically [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiBaseBoard + internal sealed class WmiBaseBoard { public string Caption; public string[] ConfigOptions; @@ -1368,7 +1368,7 @@ internal class WmiBaseBoard } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiBios : WmiClassBase + internal sealed class WmiBios : WmiClassBase { public ushort[] BiosCharacteristics; public string[] BIOSVersion; @@ -1403,7 +1403,7 @@ internal class WmiBios : WmiClassBase } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiComputerSystem + internal sealed class WmiComputerSystem { public ushort? AdminPasswordStatus; public bool? AutomaticManagedPagefile; @@ -1486,7 +1486,7 @@ public PowerManagementCapabilities[] GetPowerManagementCapabilities() } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiDeviceGuard + internal sealed class WmiDeviceGuard { public uint[] AvailableSecurityProperties; public uint? CodeIntegrityPolicyEnforcementStatus; @@ -1561,7 +1561,7 @@ public DeviceGuard AsOutputType } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiKeyboard + internal sealed class WmiKeyboard { public ushort? Availability; public string Caption; @@ -1588,14 +1588,14 @@ internal class WmiKeyboard } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WMiLogicalMemory + internal sealed class WMiLogicalMemory { // TODO: fill this in!!! public uint? TotalPhysicalMemory; } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiMsftNetAdapter + internal sealed class WmiMsftNetAdapter { public string Caption; public string Description; @@ -1683,7 +1683,7 @@ internal class WmiMsftNetAdapter } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiNetworkAdapter + internal sealed class WmiNetworkAdapter { public string AdapterType; public ushort? AdapterTypeID; @@ -1727,7 +1727,7 @@ internal class WmiNetworkAdapter } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiNetworkAdapterConfiguration + internal sealed class WmiNetworkAdapterConfiguration { public bool? ArpAlwaysSourceRoute; public bool? ArpUseEtherSNAP; @@ -1793,7 +1793,7 @@ internal class WmiNetworkAdapterConfiguration } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiOperatingSystem : WmiClassBase + internal sealed class WmiOperatingSystem : WmiClassBase { #region Fields public string BootDevice; @@ -1900,7 +1900,7 @@ private static OSProductSuite[] MakeProductSuites(uint? suiteMask) } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiPageFileUsage + internal sealed class WmiPageFileUsage { public uint? AllocatedBaseSize; public string Caption; @@ -1914,7 +1914,7 @@ internal class WmiPageFileUsage } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiProcessor + internal sealed class WmiProcessor { public ushort? AddressWidth; public ushort? Architecture; @@ -1977,7 +1977,7 @@ internal class WmiProcessor #endregion Intermediate WMI classes #region Other Intermediate classes - internal class RegWinNtCurrentVersion + internal sealed class RegWinNtCurrentVersion { public string BuildLabEx; public string CurrentVersion; diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index 2e07a945a00..c959cfcb33e 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -2841,7 +2841,7 @@ internal struct PROCESS_INFORMATION } [StructLayout(LayoutKind.Sequential)] - internal class SECURITY_ATTRIBUTES + internal sealed class SECURITY_ATTRIBUTES { public int nLength; public SafeLocalMemHandle lpSecurityDescriptor; @@ -2879,7 +2879,7 @@ protected override bool ReleaseHandle() } [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; diff --git a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs index f4c252c4d1d..3182c04c535 100644 --- a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs +++ b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs @@ -11,7 +11,7 @@ namespace Microsoft.WSMan.Management /// Class that queries the server and gets current configurations. /// Also provides a generic way to update the configurations. /// - internal class CurrentConfigurations + internal sealed class CurrentConfigurations { /// /// Prefix used to add NameSpace of root element to namespace manager. diff --git a/src/Microsoft.WSMan.Management/WsManHelper.cs b/src/Microsoft.WSMan.Management/WsManHelper.cs index 482f24aa9c1..9249c7f4b88 100644 --- a/src/Microsoft.WSMan.Management/WsManHelper.cs +++ b/src/Microsoft.WSMan.Management/WsManHelper.cs @@ -22,7 +22,7 @@ namespace Microsoft.WSMan.Management { [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")] - internal class WSManHelper + internal sealed class WSManHelper { // regular expressions private const string PTRN_URI_LAST = @"([a-z_][-a-z0-9._]*)$"; @@ -89,7 +89,7 @@ internal class WSManHelper // // // Below class is just a static container which would release sessions in case this DLL is unloaded. - internal class Sessions + internal sealed class Sessions { /// /// Dictionary object to store the connection. diff --git a/test/xUnit/csharp/test_AstDefaultVisit.cs b/test/xUnit/csharp/test_AstDefaultVisit.cs index 283c1f73071..41f3ff1ce56 100644 --- a/test/xUnit/csharp/test_AstDefaultVisit.cs +++ b/test/xUnit/csharp/test_AstDefaultVisit.cs @@ -8,17 +8,17 @@ namespace PSTests.Parallel { - internal class MyICustomAstVisitor2 : ICustomAstVisitor2 + internal sealed class MyICustomAstVisitor2 : ICustomAstVisitor2 { public object DefaultVisit(Ast ast) => ast.GetType().Name; } - internal class MyDefaultCustomAstVisitor2 : DefaultCustomAstVisitor2 + internal sealed class MyDefaultCustomAstVisitor2 : DefaultCustomAstVisitor2 { public override object DefaultVisit(Ast ast) => ast.GetType().Name; } - internal class MyAstVisitor2 : AstVisitor2 + internal sealed class MyAstVisitor2 : AstVisitor2 { public List Commands { get; } From 250144e92d1d0e7f7931ce6a7d57498c94255782 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 16 Oct 2025 06:24:06 +0100 Subject: [PATCH 028/255] Fix IDE0083: UseNotPattern (#26209) --- .../CimAsyncOperation.cs | 4 +- .../CimSessionProxy.cs | 4 +- .../cmdletization/cim/cimChildJobBase.cs | 4 +- .../cmdletization/cim/cimWrapper.cs | 2 +- .../cmdletization/cim/clientSideQuery.cs | 4 +- .../commands/utility/CustomSerialization.cs | 2 +- .../FormatAndOutput/OutGridView/ColumnInfo.cs | 2 +- .../commands/utility/Get-PSBreakpoint.cs | 4 +- .../utility/ImplicitRemotingCommands.cs | 4 +- .../commands/utility/ObjectCommandComparer.cs | 2 +- .../commands/utility/OrderObjectBase.cs | 2 +- .../commands/utility/WebCmdlet/JsonObject.cs | 2 +- .../security/AclCommands.cs | 6 +-- .../common/DisplayDatabase/typeDataQuery.cs | 2 +- .../common/FormattingObjectsDeserializer.cs | 4 +- .../other/ciminstancetypeadapter.cs | 4 +- .../engine/ArgumentTypeConverterAttribute.cs | 2 +- .../engine/Attributes.cs | 4 +- .../engine/CmdletParameterBinderController.cs | 10 ++--- .../CommandCompletion/CompletionAnalysis.cs | 2 +- .../CommandCompletion/CompletionCompleters.cs | 12 ++--- .../engine/CommandProcessor.cs | 2 +- .../engine/CoreAdapter.cs | 4 +- .../engine/ErrorPackage.cs | 2 +- .../engine/InternalCommands.cs | 2 +- .../engine/LanguagePrimitives.cs | 8 ++-- .../engine/ManagementObjectAdapter.cs | 4 +- .../engine/Modules/AnalysisCache.cs | 4 +- .../engine/MshCommandRuntime.cs | 2 +- .../engine/MshObject.cs | 8 ++-- .../engine/MshObjectTypeDescriptor.cs | 4 +- .../engine/PSVersionInfo.cs | 2 +- .../engine/SessionStateContainer.cs | 6 +-- .../engine/SessionStateProviderAPIs.cs | 20 ++++----- ...SessionStateSecurityDescriptorInterface.cs | 2 +- .../engine/TypeTable.cs | 2 +- .../engine/debugger/debugger.cs | 6 +-- .../engine/hostifaces/History.cs | 2 +- .../engine/hostifaces/HostUtilities.cs | 2 +- .../engine/hostifaces/InternalHost.cs | 2 +- .../engine/hostifaces/ListModifier.cs | 2 +- .../engine/hostifaces/PSDataCollection.cs | 2 +- .../engine/hostifaces/PowerShell.cs | 4 +- .../engine/interpreter/LightCompiler.cs | 2 +- .../engine/lang/parserutils.cs | 4 +- .../engine/runtime/CompiledScriptBlock.cs | 8 ++-- .../engine/runtime/ScriptBlockToPowerShell.cs | 2 +- .../engine/serialization.cs | 20 ++++----- .../namespaces/ProviderBase.cs | 44 +++++++++---------- .../utils/PsUtils.cs | 6 +-- .../utils/RuntimeException.cs | 2 +- 51 files changed, 131 insertions(+), 131 deletions(-) diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs index 023bf652b1d..e794fd929d1 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs @@ -381,7 +381,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re if (value is PSReference cimReference) { object baseObject = GetBaseObject(cimReference.Value); - if (!(baseObject is CimInstance cimInstance)) + if (baseObject is not CimInstance cimInstance) { return null; } @@ -403,7 +403,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re CimInstance[] cimInstanceArray = new CimInstance[cimReferenceArray.Length]; for (int i = 0; i < cimReferenceArray.Length; i++) { - if (!(cimReferenceArray[i] is PSReference tempCimReference)) + if (cimReferenceArray[i] is not PSReference tempCimReference) { return null; } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs index 630bb34dfb1..5cdc168ae24 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs @@ -2041,7 +2041,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) } CimWriteResultObject writeResultObject = args.Action as CimWriteResultObject; - if (!(writeResultObject.Result is CimClass cimClass)) + if (writeResultObject.Result is not CimClass cimClass) { return true; } @@ -2200,7 +2200,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) } CimWriteResultObject writeResultObject = args.Action as CimWriteResultObject; - if (!(writeResultObject.Result is CimInstance cimInstance)) + if (writeResultObject.Result is not CimInstance cimInstance) { return true; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs index d787389e9f0..6bd2bee7fee 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs @@ -84,7 +84,7 @@ private enum WsManErrorCode : uint private static bool IsWsManQuotaReached(Exception exception) { - if (!(exception is CimException cimException)) + if (exception is not CimException cimException) { return false; } @@ -1002,7 +1002,7 @@ private CimResponseType PromptUserCallback(string message, CimPromptType promptT internal static bool IsShowComputerNameMarkerPresent(CimInstance cimInstance) { PSObject pso = PSObject.AsPSObject(cimInstance); - if (!(pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] is PSPropertyInfo psShowComputerNameProperty)) + if (pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] is not PSPropertyInfo psShowComputerNameProperty) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs index c812778456a..472046b192f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs @@ -168,7 +168,7 @@ private CimJobContext CreateJobContext(CimSession session, object targetObject) /// object that performs a query against the wrapped object model. internal override StartableJob CreateQueryJob(CimSession session, QueryBuilder baseQuery) { - if (!(baseQuery is CimQuery query)) + if (baseQuery is not CimQuery query) { throw new ArgumentNullException(nameof(baseQuery)); } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs index c8cee5d0111..c1a8552db8c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs @@ -569,7 +569,7 @@ private static bool ActualValueGreaterThanOrEqualToExpectedValue(string property { try { - if (!(expectedPropertyValue is IComparable expectedComparable)) + if (expectedPropertyValue is not IComparable expectedComparable) { return false; } @@ -604,7 +604,7 @@ private static bool ActualValueLessThanOrEqualToExpectedValue(string propertyNam { try { - if (!(actualPropertyValue is IComparable actualComparable)) + if (actualPropertyValue is not IComparable actualComparable) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs index f8a39cce096..6d224d29007 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs @@ -704,7 +704,7 @@ private void WriteMemberInfoCollection( continue; } - if (!(info is PSPropertyInfo property)) + if (info is not PSPropertyInfo property) { continue; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs index a42462737ea..65efee78a28 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs @@ -48,7 +48,7 @@ internal Type GetValueType(PSObject liveObject, out object columnValue) /// The source string limited in the number of lines. internal static object LimitString(object src) { - if (!(src is string srcString)) + if (src is not string srcString) { return src; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs index 85bb8fa53b7..c675a0f6dc1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs @@ -115,7 +115,7 @@ protected override void ProcessRecord() Command, (Breakpoint breakpoint, string command) => { - if (!(breakpoint is CommandBreakpoint commandBreakpoint)) + if (breakpoint is not CommandBreakpoint commandBreakpoint) { return false; } @@ -130,7 +130,7 @@ protected override void ProcessRecord() Variable, (Breakpoint breakpoint, string variable) => { - if (!(breakpoint is VariableBreakpoint variableBreakpoint)) + if (breakpoint is not VariableBreakpoint variableBreakpoint) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs index 16b7db47d41..d01d144caca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs @@ -2620,7 +2620,7 @@ private string GenerateConnectionStringForNewRunspace() private string GenerateAllowRedirectionParameter() { - if (!(_remoteRunspaceInfo.Runspace.ConnectionInfo is WSManConnectionInfo wsmanConnectionInfo)) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo wsmanConnectionInfo) { return string.Empty; } @@ -2646,7 +2646,7 @@ private string GenerateAuthenticationMechanismParameter() return string.Empty; } - if (!(_remoteRunspaceInfo.Runspace.ConnectionInfo is WSManConnectionInfo wsmanConnectionInfo)) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo wsmanConnectionInfo) { return string.Empty; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs index fd01768d5a8..13b0234a02b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs @@ -77,7 +77,7 @@ internal CultureInfo Culture /// True if both the objects are same or else returns false. public override bool Equals(object inputObject) { - if (!(inputObject is ObjectCommandPropertyValue objectCommandPropertyValueObject)) + if (inputObject is not ObjectCommandPropertyValue objectCommandPropertyValueObject) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs index 178eaa12f62..596bbcaafc6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs @@ -358,7 +358,7 @@ internal static string[] GetDefaultKeyPropertySet(PSObject mshObj) return null; } - if (!(standardNames.Members["DefaultKeyPropertySet"] is PSPropertySet defaultKeys)) + if (standardNames.Members["DefaultKeyPropertySet"] is not PSPropertySet defaultKeys) { return null; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs index b626fd71db8..6506f2bd2ce 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -662,7 +662,7 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso /// private static object AddPsProperties(object psObj, object obj, int depth, bool isPurePSObj, bool isCustomObj, in ConvertToJsonContext context) { - if (!(psObj is PSObject pso)) + if (psObj is not PSObject pso) { return obj; } diff --git a/src/Microsoft.PowerShell.Security/security/AclCommands.cs b/src/Microsoft.PowerShell.Security/security/AclCommands.cs index 1b5beffa49e..e39b296d154 100644 --- a/src/Microsoft.PowerShell.Security/security/AclCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/AclCommands.cs @@ -207,7 +207,7 @@ public static string GetOwner(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ObjectSecurity sd)) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -246,7 +246,7 @@ public static string GetGroup(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ObjectSecurity sd)) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -570,7 +570,7 @@ public static string GetSddl(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ObjectSecurity sd)) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs index c759cabd2a1..de6df333951 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs @@ -606,7 +606,7 @@ internal static AppliesTo GetAllApplicableTypes(TypeInfoDataBase db, AppliesTo a else { // check if we have a type group reference - if (!(r is TypeGroupReference tgr)) + if (r is not TypeGroupReference tgr) continue; // find the type group definition the reference points to diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs index 00923a103ec..7ae144702ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs @@ -54,7 +54,7 @@ fid is GroupEndData || return false; } - if (!(GetProperty(so, FormatInfoData.classidProperty) is string classId)) + if (GetProperty(so, FormatInfoData.classidProperty) is not string classId) { // it's not one of the objects derived from FormatInfoData return false; @@ -109,7 +109,7 @@ fid is GroupEndData || return so; } - if (!(GetProperty(so, FormatInfoData.classidProperty) is string classId)) + if (GetProperty(so, FormatInfoData.classidProperty) is not string classId) { // it's not one of the objects derived from FormatInfoData, // just return it as is diff --git a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs index 9a3e5f137ad..202c3e98e48 100644 --- a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs +++ b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs @@ -267,7 +267,7 @@ private static List GetInheritanceChain(CimInstance cimInstance) /// public override Collection GetTypeNameHierarchy(object baseObject) { - if (!(baseObject is CimInstance cimInstance)) + if (baseObject is not CimInstance cimInstance) { throw new ArgumentNullException(nameof(baseObject)); } @@ -343,7 +343,7 @@ public override bool IsSettable(PSAdaptedProperty adaptedProperty) return false; } - if (!(adaptedProperty.Tag is CimProperty cimProperty)) + if (adaptedProperty.Tag is not CimProperty cimProperty) { return false; } diff --git a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs index a2c2cd8321e..596a4142e93 100644 --- a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs +++ b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs @@ -65,7 +65,7 @@ internal object Transform(EngineIntrinsics engineIntrinsics, object inputData, b else temp = result; - if (!(temp is PSReference reference)) + if (temp is not PSReference reference) { throw new PSInvalidCastException("InvalidCastExceptionReferenceTypeExpected", null, ExtendedTypeSystem.ReferenceTypeExpected); diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 2f7de2a2149..31c520498c0 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -844,7 +844,7 @@ public sealed class ValidateLengthAttribute : ValidateEnumeratedArgumentsAttribu /// For invalid arguments. protected override void ValidateElement(object element) { - if (!(element is string objectString)) + if (element is not string objectString) { throw new ValidationMetadataException( "ValidateLengthNotString", @@ -1956,7 +1956,7 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin Metadata.ValidateNotNullFailure); } - if (!(arguments is string path)) + if (arguments is not string path) { throw new ValidationMetadataException( "PathArgumentIsNotValid", diff --git a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs index 88fadc3342d..b9cf67b29e6 100644 --- a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs +++ b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs @@ -622,7 +622,7 @@ private Dictionary GetDefaultParameterVa foreach (DictionaryEntry entry in DefaultParameterValues) { - if (!(entry.Key is string key)) + if (entry.Key is not string key) { continue; } @@ -4386,7 +4386,7 @@ public override bool ContainsKey(object key) throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { return false; } @@ -4415,7 +4415,7 @@ private void AddImpl(object key, object value, bool isSelfIndexing) throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { throw PSTraceSource.NewArgumentException(nameof(key), ParameterBinderStrings.StringValueKeyExpected, key, key.GetType().FullName); } @@ -4463,7 +4463,7 @@ public override object this[object key] throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { return null; } @@ -4489,7 +4489,7 @@ public override void Remove(object key) throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { return; } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 42a60ab7e0e..e4d8203a63b 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -262,7 +262,7 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs if (lastAst.Parent is CommandExpressionAst) { // Handle "switch -file m" or "switch -file *.ps1" - if (!(lastAst.Parent.Parent is PipelineAst pipeline)) + if (lastAst.Parent.Parent is not PipelineAst pipeline) { return false; } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 2b90bc02837..16b0d1edac4 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -602,7 +602,7 @@ internal static List CompleteCommandParameter(CompletionContex else { // No CommandParameterAst is found. It could be a StringConstantExpressionAst "-" - if (!(context.RelatedAsts[context.RelatedAsts.Count - 1] is StringConstantExpressionAst dashAst)) + if (context.RelatedAsts[context.RelatedAsts.Count - 1] is not StringConstantExpressionAst dashAst) return result; if (!dashAst.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase)) return result; @@ -3130,7 +3130,7 @@ private static void NativeCompletionCimNamespace( continue; } - if (!(namespaceNameProperty.Value is string childNamespace)) + if (namespaceNameProperty.Value is not string childNamespace) { continue; } @@ -6059,7 +6059,7 @@ internal static List CompleteComment(CompletionContext context for (int index = psobjs.Count - 1; index >= 0; index--) { var psobj = psobjs[index]; - if (!(PSObject.Base(psobj) is HistoryInfo historyInfo)) continue; + if (PSObject.Base(psobj) is not HistoryInfo historyInfo) continue; var commandLine = historyInfo.CommandLine; if (!string.IsNullOrEmpty(commandLine) && pattern.IsMatch(commandLine)) @@ -8560,7 +8560,7 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand var varValues = new List(); foreach (ExpressionAst nestedAst in expandableStringAst.NestedExpressions) { - if (!(nestedAst is VariableExpressionAst variableAst)) { return false; } + if (nestedAst is not VariableExpressionAst variableAst) { return false; } string strValue = CombineVariableWithPartialPath(variableAst, null, executionContext); if (strValue != null) @@ -8688,7 +8688,7 @@ internal static void CompleteMemberHelper( IEnumerable members; if (@static) { - if (!(PSObject.Base(value) is Type type)) + if (PSObject.Base(value) is not Type type) { return; } @@ -8754,7 +8754,7 @@ internal static void CompleteMemberHelper( var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase); foreach (DictionaryEntry entry in dictionary) { - if (!(entry.Key is string key)) + if (entry.Key is not string key) continue; if (pattern.IsMatch(key)) diff --git a/src/System.Management.Automation/engine/CommandProcessor.cs b/src/System.Management.Automation/engine/CommandProcessor.cs index d9758cea2fa..d1ef250d717 100644 --- a/src/System.Management.Automation/engine/CommandProcessor.cs +++ b/src/System.Management.Automation/engine/CommandProcessor.cs @@ -101,7 +101,7 @@ internal CommandProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext /// internal ParameterBinderController NewParameterBinderController(InternalCommand command) { - if (!(command is Cmdlet cmdlet)) + if (command is not Cmdlet cmdlet) { throw PSTraceSource.NewArgumentException(nameof(command)); } diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index 907060bfe49..136083d04d7 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -1723,7 +1723,7 @@ internal static void SetReferences(object[] arguments, MethodInformation methodI // It still might be an PSObject wrapping an PSReference if (originalArgumentReference == null) { - if (!(originalArgument is PSObject originalArgumentObj)) + if (originalArgument is not PSObject originalArgumentObj) { continue; } @@ -3860,7 +3860,7 @@ internal void AddAllDynamicMembers(object obj, PSMemberInfoInternalCollection private static bool PropertyIsStatic(PSProperty property) { - if (!(property.adapterData is PropertyCacheEntry entry)) + if (property.adapterData is not PropertyCacheEntry entry) { return false; } diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index b20da137590..baa2dd215de 100644 --- a/src/System.Management.Automation/engine/ErrorPackage.cs +++ b/src/System.Management.Automation/engine/ErrorPackage.cs @@ -1665,7 +1665,7 @@ private string GetInvocationTypeName() return commandInfo.Name; } - if (!(commandInfo is CmdletInfo cmdletInfo)) + if (commandInfo is not CmdletInfo cmdletInfo) { return string.Empty; } diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 39556db9cca..844a965ac1e 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -2018,7 +2018,7 @@ private void CheckLanguageMode() private object GetLikeRHSOperand(object operand) { - if (!(operand is string val)) + if (operand is not string val) { return operand; } diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 6644223d7c6..fa9e68f5c24 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -636,7 +636,7 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP formatProvider ??= CultureInfo.InvariantCulture; - if (!(formatProvider is CultureInfo culture)) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -780,7 +780,7 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP { formatProvider ??= CultureInfo.InvariantCulture; - if (!(formatProvider is CultureInfo culture)) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -1036,7 +1036,7 @@ internal static bool IsTrue(IList objectArray) // but since we don't want this to recurse indefinitely // we explicitly check the case where it would recurse // and deal with it. - if (!(PSObject.Base(objectArray[0]) is IList firstElement)) + if (PSObject.Base(objectArray[0]) is not IList firstElement) { return IsTrue(objectArray[0]); } @@ -2100,7 +2100,7 @@ public override object ConvertFrom(object sourceValue, Type destinationType, IFo protected static object BaseConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase, bool multipleValues) { Diagnostics.Assert(sourceValue != null, "the type converter has a special case for null source values"); - if (!(sourceValue is string sourceValueString)) + if (sourceValue is not string sourceValueString) { throw new PSInvalidCastException("InvalidCastEnumFromTypeNotAString", null, ExtendedTypeSystem.InvalidCastException, diff --git a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs index aa1c151e0e3..9b69ee168d7 100644 --- a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs +++ b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs @@ -172,7 +172,7 @@ protected override T GetMember(object obj, string memberName) { tracer.WriteLine("Getting member with name {0}", memberName); - if (!(obj is ManagementBaseObject mgmtObject)) + if (obj is not ManagementBaseObject mgmtObject) { return null; } @@ -364,7 +364,7 @@ protected override object PropertyGet(PSProperty property) /// Instructs the adapter to convert before setting, if the adapter supports conversion. protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible) { - if (!(property.baseObject is ManagementBaseObject mObj)) + if (property.baseObject is not ManagementBaseObject mObj) { throw new SetValueInvocationException("CannotSetNonManagementObjectMsg", null, diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 44dc9f1b610..79085041abd 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -194,7 +194,7 @@ internal static bool ModuleIsEditionIncompatible(string modulePath, Hashtable mo internal static bool ModuleAnalysisViaGetModuleRequired(object modulePathObj, bool hadCmdlets, bool hadFunctions, bool hadAliases) { - if (!(modulePathObj is string modulePath)) + if (modulePathObj is not string modulePath) return true; if (modulePath.EndsWith(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase)) @@ -256,7 +256,7 @@ private static bool CheckModulesTypesInManifestAgainstExportedCommands(Hashtable return ModuleAnalysisViaGetModuleRequired(nestedModule, hadCmdlets, hadFunctions, hadAliases); } - if (!(nestedModules is object[] nestedModuleArray)) + if (nestedModules is not object[] nestedModuleArray) return true; foreach (var element in nestedModuleArray) diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index 2e5cdb455b7..d5f821bc930 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -2360,7 +2360,7 @@ internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPip { if (permittedToWrite == null) throw PSTraceSource.NewArgumentNullException(nameof(permittedToWrite)); - if (!(permittedToWrite.commandRuntime is MshCommandRuntime mcr)) + if (permittedToWrite.commandRuntime is not MshCommandRuntime mcr) throw PSTraceSource.NewArgumentNullException("permittedToWrite.CommandRuntime"); _pp = mcr.PipelineProcessor; if (_pp == null) diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index d788a8a3b43..c5e8bbac443 100644 --- a/src/System.Management.Automation/engine/MshObject.cs +++ b/src/System.Management.Automation/engine/MshObject.cs @@ -592,7 +592,7 @@ protected PSObject(SerializationInfo info, StreamingContext context) throw PSTraceSource.NewArgumentNullException(nameof(info)); } - if (!(info.GetValue("CliXml", typeof(string)) is string serializedData)) + if (info.GetValue("CliXml", typeof(string)) is not string serializedData) { throw PSTraceSource.NewArgumentNullException(nameof(info)); } @@ -980,7 +980,7 @@ public static implicit operator PSObject(bool valueToConvert) /// internal static object Base(object obj) { - if (!(obj is PSObject mshObj)) + if (obj is not PSObject mshObj) { return obj; } @@ -1064,7 +1064,7 @@ internal static PSObject AsPSObject(object obj, bool storeTypeNameAndInstanceMem /// internal static object GetKeyForResurrectionTables(object obj) { - if (!(obj is PSObject pso)) + if (obj is not PSObject pso) { return obj; } @@ -1851,7 +1851,7 @@ internal static object GetNoteSettingValue(PSMemberSet settings, string noteName settings.ReplicateInstance(ownerObject); } - if (!(settings.Members[noteName] is PSNoteProperty note)) + if (settings.Members[noteName] is not PSNoteProperty note) { return defaultValue; } diff --git a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs index 11955713c7e..113161748c9 100644 --- a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs +++ b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs @@ -219,7 +219,7 @@ private static PSObject GetComponentPSObject(object component) PSObject mshObj = component as PSObject; if (mshObj == null) { - if (!(component is PSObjectTypeDescriptor descriptor)) + if (component is not PSObjectTypeDescriptor descriptor) { throw PSTraceSource.NewArgumentException(nameof(component), ExtendedTypeSystem.InvalidComponent, "component", @@ -464,7 +464,7 @@ public override PropertyDescriptorCollection GetProperties(Attribute[] attribute /// True if the Instance property of is equal to the current Instance; otherwise, false. public override bool Equals(object obj) { - if (!(obj is PSObjectTypeDescriptor other)) + if (obj is not PSObjectTypeDescriptor other) { return false; } diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index 75f75cc112f..eef9819a568 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -795,7 +795,7 @@ public int CompareTo(object version) return 1; } - if (!(version is SemanticVersion v)) + if (version is not SemanticVersion v) { throw PSTraceSource.NewArgumentException(nameof(version)); } diff --git a/src/System.Management.Automation/engine/SessionStateContainer.cs b/src/System.Management.Automation/engine/SessionStateContainer.cs index e97d62c87d1..fa8884b92e8 100644 --- a/src/System.Management.Automation/engine/SessionStateContainer.cs +++ b/src/System.Management.Automation/engine/SessionStateContainer.cs @@ -1839,7 +1839,7 @@ private void ProcessPathItems( return; } - if (!(childNameObjects[index].BaseObject is string childName)) + if (childNameObjects[index].BaseObject is not string childName) { continue; } @@ -2584,7 +2584,7 @@ private void DoGetChildNamesManually( return; } - if (!(result.BaseObject is string name)) + if (result.BaseObject is not string name) { continue; } @@ -2632,7 +2632,7 @@ private void DoGetChildNamesManually( return; } - if (!(result.BaseObject is string name)) + if (result.BaseObject is not string name) { continue; } diff --git a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs index 0c94c9ea059..3ad2ed99e31 100644 --- a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs @@ -350,7 +350,7 @@ internal DriveCmdletProvider GetDriveProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - if (!(GetProviderInstance(providerId) is DriveCmdletProvider driveCmdletProvider)) + if (GetProviderInstance(providerId) is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -382,7 +382,7 @@ internal DriveCmdletProvider GetDriveProviderInstance(ProviderInfo provider) throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is DriveCmdletProvider driveCmdletProvider)) + if (GetProviderInstance(provider) is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -414,7 +414,7 @@ private static DriveCmdletProvider GetDriveProviderInstance(CmdletProvider provi throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is DriveCmdletProvider driveCmdletProvider)) + if (providerInstance is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -449,7 +449,7 @@ internal ItemCmdletProvider GetItemProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - if (!(GetProviderInstance(providerId) is ItemCmdletProvider itemCmdletProvider)) + if (GetProviderInstance(providerId) is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -481,7 +481,7 @@ internal ItemCmdletProvider GetItemProviderInstance(ProviderInfo provider) throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is ItemCmdletProvider itemCmdletProvider)) + if (GetProviderInstance(provider) is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -513,7 +513,7 @@ private static ItemCmdletProvider GetItemProviderInstance(CmdletProvider provide throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is ItemCmdletProvider itemCmdletProvider)) + if (providerInstance is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -548,7 +548,7 @@ internal ContainerCmdletProvider GetContainerProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - if (!(GetProviderInstance(providerId) is ContainerCmdletProvider containerCmdletProvider)) + if (GetProviderInstance(providerId) is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -580,7 +580,7 @@ internal ContainerCmdletProvider GetContainerProviderInstance(ProviderInfo provi throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is ContainerCmdletProvider containerCmdletProvider)) + if (GetProviderInstance(provider) is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -612,7 +612,7 @@ private static ContainerCmdletProvider GetContainerProviderInstance(CmdletProvid throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is ContainerCmdletProvider containerCmdletProvider)) + if (providerInstance is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -644,7 +644,7 @@ internal NavigationCmdletProvider GetNavigationProviderInstance(ProviderInfo pro throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is NavigationCmdletProvider navigationCmdletProvider)) + if (GetProviderInstance(provider) is not NavigationCmdletProvider navigationCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.NavigationCmdletProvider_NotSupported); diff --git a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs index 16ebe0fc3b5..08635c7efe1 100644 --- a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs +++ b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs @@ -35,7 +35,7 @@ internal static ISecurityDescriptorCmdletProvider GetPermissionProviderInstance( throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is ISecurityDescriptorCmdletProvider permissionCmdletProvider)) + if (providerInstance is not ISecurityDescriptorCmdletProvider permissionCmdletProvider) { throw PSTraceSource.NewNotSupportedException( diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index bffeb9e0ceb..a0eead95e23 100644 --- a/src/System.Management.Automation/engine/TypeTable.cs +++ b/src/System.Management.Automation/engine/TypeTable.cs @@ -3922,7 +3922,7 @@ internal Collection GetSpecificProperties(ConsolidatedString types) } PSMemberSet settings = typeMembers[PSStandardMembers] as PSMemberSet; - if (!(settings?.Members[PropertySerializationSet] is PSPropertySet typeProperties)) + if (settings?.Members[PropertySerializationSet] is not PSPropertySet typeProperties) { continue; } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index bdad4960ac0..e093210f960 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -2368,7 +2368,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC // // Otherwise let root script debugger handle it. // - if (!(_context.CurrentRunspace is LocalRunspace localRunspace)) + if (_context.CurrentRunspace is not LocalRunspace localRunspace) { throw new PSInvalidOperationException( DebuggerStrings.CannotProcessDebuggerCommandNotStopped, @@ -3829,7 +3829,7 @@ private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEvent } // Get nested debugger runspace info. - if (!(senderDebugger is NestedRunspaceDebugger nestedDebugger)) { return; } + if (senderDebugger is not NestedRunspaceDebugger nestedDebugger) { return; } PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType; @@ -4686,7 +4686,7 @@ protected override void HandleDebuggerStop(object sender, DebuggerStopEventArgs private object DrainAndBlockRemoteOutput() { // We do this only for remote runspaces. - if (!(_runspace is RemoteRunspace remoteRunspace)) { return null; } + if (_runspace is not RemoteRunspace remoteRunspace) { return null; } var runningPowerShell = remoteRunspace.GetCurrentBasePowerShell(); if (runningPowerShell != null) diff --git a/src/System.Management.Automation/engine/hostifaces/History.cs b/src/System.Management.Automation/engine/hostifaces/History.cs index 3b503f2fade..00a090d4b22 100644 --- a/src/System.Management.Automation/engine/hostifaces/History.cs +++ b/src/System.Management.Automation/engine/hostifaces/History.cs @@ -1436,7 +1436,7 @@ void ProcessRecord() } // Read CommandLine property - if (!(GetPropertyValue(mshObject, "CommandLine") is string commandLine)) + if (GetPropertyValue(mshObject, "CommandLine") is not string commandLine) { break; } diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index 003625791b1..ba71fef0443 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -284,7 +284,7 @@ internal static string GetMaxLines(string source, int maxLines) internal static List GetSuggestion(Runspace runspace) { - if (!(runspace is LocalRunspace localRunspace)) + if (runspace is not LocalRunspace localRunspace) { return new List(); } diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs index 450941b4b63..c60bff90303 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs @@ -448,7 +448,7 @@ public override void NotifyEndApplication() /// private IHostSupportsInteractiveSession GetIHostSupportsInteractiveSession() { - if (!(_externalHostRef.Value is IHostSupportsInteractiveSession host)) + if (_externalHostRef.Value is not IHostSupportsInteractiveSession host) { throw new PSNotImplementedException(); } diff --git a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs index db089b68333..aab1cb2e777 100644 --- a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs +++ b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs @@ -216,7 +216,7 @@ public void ApplyTo(object collectionToUpdate) collectionToUpdate = PSObject.Base(collectionToUpdate); - if (!(collectionToUpdate is IList list)) + if (collectionToUpdate is not IList list) { throw PSTraceSource.NewInvalidOperationException(PSListModifierStrings.UpdateFailed); } diff --git a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs index eb1a796d0e7..a0945e21df1 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs @@ -335,7 +335,7 @@ protected PSDataCollection(SerializationInfo info, StreamingContext context) throw PSTraceSource.NewArgumentNullException(nameof(info)); } - if (!(info.GetValue("Data", typeof(IList)) is IList listToUse)) + if (info.GetValue("Data", typeof(IList)) is not IList listToUse) { throw PSTraceSource.NewArgumentNullException(nameof(info)); } diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs index 7d29ffa5223..7073437d7e9 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs @@ -1369,7 +1369,7 @@ public PowerShell AddParameters(IDictionary parameters) foreach (DictionaryEntry entry in parameters) { - if (!(entry.Key is string parameterName)) + if (entry.Key is not string parameterName) { throw PSTraceSource.NewArgumentException(nameof(parameters), PowerShellStrings.KeyMustBeString); } @@ -3348,7 +3348,7 @@ public Task> InvokeAsync(PSDataColle /// private IAsyncResult BeginBatchInvoke(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings, AsyncCallback callback, object state) { - if (!((object)output is PSDataCollection asyncOutput)) + if ((object)output is not PSDataCollection asyncOutput) { throw PSTraceSource.NewInvalidOperationException(); } diff --git a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs index a006b7e0f4f..c117fbff33a 100644 --- a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs +++ b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs @@ -1298,7 +1298,7 @@ private bool TryPushLabelBlock(Expression node) private void DefineBlockLabels(Expression node) { - if (!(node is BlockExpression block)) + if (node is not BlockExpression block) { return; } diff --git a/src/System.Management.Automation/engine/lang/parserutils.cs b/src/System.Management.Automation/engine/lang/parserutils.cs index eb438a8a057..184c82c6815 100644 --- a/src/System.Management.Automation/engine/lang/parserutils.cs +++ b/src/System.Management.Automation/engine/lang/parserutils.cs @@ -1466,7 +1466,7 @@ internal static string GetTypeFullName(object obj) return string.Empty; } - if (!(obj is PSObject mshObj)) + if (obj is not PSObject mshObj) { return obj.GetType().FullName; } @@ -1570,7 +1570,7 @@ internal static object CallMethod( // not really a method call. if (valueToSet != AutomationNull.Value) { - if (!(targetMethod is PSParameterizedProperty propertyToSet)) + if (targetMethod is not PSParameterizedProperty propertyToSet) { throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), errorPosition, "ParameterizedPropertyAssignmentFailed", ParserStrings.ParameterizedPropertyAssignmentFailed, GetTypeFullName(target), methodName); diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 2569dd3a492..f4829bc3547 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -196,7 +196,7 @@ private void ReallyCompile(bool optimize) private void PerformSecurityChecks() { - if (!(Ast is ScriptBlockAst scriptBlockAst)) + if (Ast is not ScriptBlockAst scriptBlockAst) { // Checks are only needed at the top level. return; @@ -266,12 +266,12 @@ bool IsScriptBlockInFactASafeHashtable() return false; } - if (!(endBlock.Statements[0] is PipelineAst pipelineAst)) + if (endBlock.Statements[0] is not PipelineAst pipelineAst) { return false; } - if (!(pipelineAst.GetPureExpression() is HashtableAst hashtableAst)) + if (pipelineAst.GetPureExpression() is not HashtableAst hashtableAst) { return false; } @@ -769,7 +769,7 @@ private PipelineAst GetSimplePipeline(Func errorHandler) return errorHandler(AutomationExceptions.CantConvertScriptBlockWithTrap); } - if (!(statements[0] is PipelineAst pipeAst)) + if (statements[0] is not PipelineAst pipeAst) { return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline); } diff --git a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs index b0017080571..d0b7ef20680 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -542,7 +542,7 @@ private static Tuple, object[]> GetUsingValues( if (variables != null) { - if (!(usingAst.SubExpression is VariableExpressionAst variableAst)) + if (usingAst.SubExpression is not VariableExpressionAst variableAst) { throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), usingAst.Extent, "CantGetUsingExpressionValueWithSpecifiedVariableDictionary", AutomationExceptions.CantGetUsingExpressionValueWithSpecifiedVariableDictionary, usingAst.Extent.Text); diff --git a/src/System.Management.Automation/engine/serialization.cs b/src/System.Management.Automation/engine/serialization.cs index 3cffd998d49..add0eab25dc 100644 --- a/src/System.Management.Automation/engine/serialization.cs +++ b/src/System.Management.Automation/engine/serialization.cs @@ -2018,7 +2018,7 @@ int depth foreach (PSMemberInfo info in propertyCollection) { - if (!(info is PSProperty prop)) + if (info is not PSProperty prop) { continue; } @@ -3373,28 +3373,28 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) PSObject psoDeserializedClass = PSObject.AsPSObject(deserializedClass); - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimNamespaceProperty] is PSPropertyInfo namespaceProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimNamespaceProperty] is not PSPropertyInfo namespaceProperty) { return null; } string cimNamespace = namespaceProperty.Value as string; - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimClassNameProperty] is PSPropertyInfo classNameProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimClassNameProperty] is not PSPropertyInfo classNameProperty) { return null; } string cimClassName = classNameProperty.Value as string; - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimServerNameProperty] is PSPropertyInfo computerNameProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimServerNameProperty] is not PSPropertyInfo computerNameProperty) { return null; } string computerName = computerNameProperty.Value as string; - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimHashCodeProperty] is PSPropertyInfo hashCodeProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimHashCodeProperty] is not PSPropertyInfo hashCodeProperty) { return null; } @@ -3511,7 +3511,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) { foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.AdaptedMembers) { - if (!(deserializedMemberInfo is PSPropertyInfo deserializedProperty)) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -3531,7 +3531,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) // process properties that were originally "extended" properties foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.InstanceMembers) { - if (!(deserializedMemberInfo is PSPropertyInfo deserializedProperty)) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -7345,7 +7345,7 @@ public static UInt32 GetParameterSetMetadataFlags(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ParameterSetMetadata parameterSetMetadata)) + if (instance.BaseObject is not ParameterSetMetadata parameterSetMetadata) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7366,7 +7366,7 @@ public static PSObject GetInvocationInfo(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is DebuggerStopEventArgs dbgStopEventArgs)) + if (instance.BaseObject is not DebuggerStopEventArgs dbgStopEventArgs) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7625,7 +7625,7 @@ public static Guid GetFormatViewDefinitionInstanceId(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is FormatViewDefinition formatViewDefinition)) + if (instance.BaseObject is not FormatViewDefinition formatViewDefinition) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } diff --git a/src/System.Management.Automation/namespaces/ProviderBase.cs b/src/System.Management.Automation/namespaces/ProviderBase.cs index 07be68dc3ec..4345ec9f8ec 100644 --- a/src/System.Management.Automation/namespaces/ProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ProviderBase.cs @@ -260,7 +260,7 @@ internal void GetProperty( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -298,7 +298,7 @@ internal object GetPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -327,7 +327,7 @@ internal void SetProperty( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -365,7 +365,7 @@ internal object SetPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -397,7 +397,7 @@ internal void ClearProperty( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -435,7 +435,7 @@ internal object ClearPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -479,7 +479,7 @@ internal void NewProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -524,7 +524,7 @@ internal object NewPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -556,7 +556,7 @@ internal void RemoveProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -593,7 +593,7 @@ internal object RemovePropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -629,7 +629,7 @@ internal void RenameProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -670,7 +670,7 @@ internal object RenamePropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -710,7 +710,7 @@ internal void CopyProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -755,7 +755,7 @@ internal object CopyPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -795,7 +795,7 @@ internal void MoveProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -840,7 +840,7 @@ internal object MovePropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -871,7 +871,7 @@ internal IContentReader GetContentReader( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -904,7 +904,7 @@ internal object GetContentReaderDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -931,7 +931,7 @@ internal IContentWriter GetContentWriter( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -964,7 +964,7 @@ internal object GetContentWriterDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -988,7 +988,7 @@ internal void ClearContent( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1021,7 +1021,7 @@ internal object ClearContentDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { return null; } diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index ff2d0ade5ea..0a4682bcdd6 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -308,7 +308,7 @@ internal static Hashtable EvaluatePowerShellDataFile( ex.Message); } - if (!(evaluationResult is Hashtable retResult)) + if (evaluationResult is not Hashtable retResult) { throw PSTraceSource.NewInvalidOperationException( ParserStrings.InvalidPowerShellDataFile, @@ -453,14 +453,14 @@ internal static object[] Base64ToArgsConverter(string base64) throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } - if (!(dso is PSObject mo)) + if (dso is not PSObject mo) { // This helper function should move the host. Provide appropriate error message. // Format of args parameter is not correct. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } - if (!(mo.BaseObject is ArrayList argsList)) + if (mo.BaseObject is not ArrayList argsList) { // This helper function should move the host. Provide appropriate error message. // Format of args parameter is not correct. diff --git a/src/System.Management.Automation/utils/RuntimeException.cs b/src/System.Management.Automation/utils/RuntimeException.cs index 4cfdc31bcb6..61a3a345eff 100644 --- a/src/System.Management.Automation/utils/RuntimeException.cs +++ b/src/System.Management.Automation/utils/RuntimeException.cs @@ -221,7 +221,7 @@ internal static string RetrieveMessage(Exception e) if (e == null) return string.Empty; - if (!(e is IContainsErrorRecord icer)) + if (e is not IContainsErrorRecord icer) return e.Message; ErrorRecord er = icer.ErrorRecord; if (er == null) From a10ff105c987c7ca3400dfdba05ca80a9688e89b Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:58:28 +0100 Subject: [PATCH 029/255] Fix `SA1028`: Code should not contain trailing whitespace. Part 1. (#26203) --- .../ManagementList/ColumnPicker.xaml.cs | 2 +- .../cmdletization/cim/CimJobException.cs | 2 +- .../cimSupport/cmdletization/cim/cimQuery.cs | 2 +- .../commands/management/CIMHelper.cs | 2 +- .../commands/management/Eventlog.cs | 4 +- .../management/GetComputerInfoCommand.cs | 2 +- .../commands/management/ResolvePathCommand.cs | 2 +- .../commands/management/TestPathCommand.cs | 2 +- .../commands/management/WebServiceProxy.cs | 2 +- .../commands/utility/CsvCommands.cs | 10 ++--- .../commands/utility/Import-LocalizedData.cs | 2 +- .../commands/utility/Measure-Object.cs | 2 +- .../commands/utility/New-Object.cs | 4 +- .../commands/utility/NewGuidCommand.cs | 2 +- .../commands/utility/StartSleepCommand.cs | 2 +- .../Common/HttpVersionCompletionsAttribute.cs | 2 +- .../utility/WebCmdlet/ConvertToJsonCommand.cs | 2 +- .../utility/WebCmdlet/CoreCLR/WebProxy.cs | 2 +- .../utility/trace/TraceExpressionCommand.cs | 2 +- .../WindowsTaskbarJumpList/HResult.cs | 12 +++--- .../host/msh/Executor.cs | 2 +- .../host/msh/StopTranscriptCmdlet.cs | 2 +- src/Microsoft.WSMan.Management/CredSSP.cs | 2 +- .../DscSupport/CimDSCParser.cs | 2 +- .../common/DisplayDatabase/FormatTable.cs | 2 +- ...lets-over-objects.xmlSerializer.autogen.cs | 2 +- .../CommandCompletion/CompletionAnalysis.cs | 4 +- .../CommandCompletion/CompletionCompleters.cs | 14 +++---- .../CommandCompletion/CompletionHelpers.cs | 6 +-- .../engine/CommandPathSearch.cs | 2 +- .../engine/CommandSearcher.cs | 2 +- .../engine/EventManager.cs | 2 +- .../engine/GetCommandCommand.cs | 2 +- .../engine/LanguagePrimitives.cs | 4 +- .../engine/Modules/GetModuleCommand.cs | 2 +- .../engine/Modules/ModuleCmdletBase.cs | 4 +- .../engine/NativeCommandParameterBinder.cs | 4 +- .../engine/debugger/debugger.cs | 6 +-- .../engine/hostifaces/LocalConnection.cs | 4 +- .../engine/hostifaces/PSTask.cs | 2 +- .../engine/hostifaces/Pipeline.cs | 2 +- .../engine/hostifaces/PowerShell.cs | 8 ++-- .../engine/hostifaces/RunspacePool.cs | 2 +- .../engine/hostifaces/RunspacePoolInternal.cs | 2 +- .../engine/regex.cs | 4 +- .../engine/runtime/ScriptBlockToPowerShell.cs | 14 +++---- .../namespaces/RegistryProvider.cs | 2 +- .../config/MshSnapinLoadException.cs | 2 +- .../utils/CommandDiscoveryExceptions.cs | 6 +-- .../utils/CommandProcessorExceptions.cs | 2 +- .../utils/ExecutionExceptions.cs | 42 +++++++++---------- .../utils/HostInterfacesExceptions.cs | 6 +-- .../utils/MshArgumentException.cs | 2 +- .../utils/MshArgumentNullException.cs | 4 +- .../utils/MshArgumentOutOfRangeException.cs | 4 +- .../utils/MshInvalidOperationException.cs | 2 +- .../utils/MshNotImplementedException.cs | 4 +- .../utils/MshNotSupportedException.cs | 4 +- .../utils/ParameterBinderExceptions.cs | 12 +++--- .../utils/RuntimeException.cs | 4 +- .../EtwActivityReverterMethodInvoker.cs | 2 +- .../utils/tracing/PSEtwLogProvider.cs | 2 +- test/perf/benchmarks/Engine.Compiler.cs | 2 +- .../BenchmarkDotNet.Extensions/Extensions.cs | 2 +- .../PartitionFilter.cs | 4 +- .../TooManyTestCasesValidator.cs | 4 +- .../UniqueArgumentsValidator.cs | 2 +- .../ValuesGenerator.cs | 6 +-- test/perf/dotnet-tools/Reporting/Reporter.cs | 8 ++-- .../dotnet-tools/ResultsComparer/Program.cs | 2 +- .../assets/ExpTest/ExpTest.cs | 2 +- test/xUnit/csharp/test_CryptoUtils.cs | 2 +- test/xUnit/csharp/test_FileSystemProvider.cs | 2 +- test/xUnit/csharp/test_PSConfiguration.cs | 2 +- 74 files changed, 151 insertions(+), 151 deletions(-) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs index 8a919959968..05151330ea2 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs @@ -60,7 +60,7 @@ internal ColumnPicker( : this() { ArgumentNullException.ThrowIfNull(columns); - + ArgumentNullException.ThrowIfNull(availableColumns); // Add visible columns to Selected list, preserving order diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs index 675c6c71404..935cc960c6c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs @@ -49,7 +49,7 @@ public CimJobException(string message, Exception inner) : base(message, inner) /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CimJobException( SerializationInfo info, StreamingContext context) diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs index c9f1cc5cafa..f08c2ab3c11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs @@ -315,7 +315,7 @@ public override void FilterByAssociatedInstance(object associatedInstance, strin public override void AddQueryOption(string optionName, object optionValue) { ArgumentException.ThrowIfNullOrEmpty(optionName); - ArgumentNullException.ThrowIfNull(optionValue); + ArgumentNullException.ThrowIfNull(optionValue); this.queryOptions[optionName] = optionValue; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs index 035d5b17bbe..688b6362e16 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs @@ -132,7 +132,7 @@ internal static string WqlQueryAll(string from) internal static T[] GetAll(CimSession session, string nameSpace, string wmiClassName) where T : class, new() { ArgumentException.ThrowIfNullOrEmpty(wmiClassName); - + var rv = new List(); try diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs index ffd69951c61..c667116fdd0 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs @@ -555,7 +555,7 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!usernamematch) + if (!usernamematch) { return usernamematch; } @@ -595,7 +595,7 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!datematch) + if (!datematch) { return datematch; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs index e355be6f3ff..87f96c436ea 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs @@ -3685,7 +3685,7 @@ public enum DeviceGuardHardwareSecure /// Secure Memory Overwrite. /// SecureMemoryOverwrite = 4, - + /// /// UEFI Code Readonly. /// diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs index 3d1b66933d2..d0f5fecf495 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -81,7 +81,7 @@ public SwitchParameter Relative /// [Parameter] public string RelativeBasePath - { + { get { return _relativeBasePath; diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs index d8748b6ef9c..50765c0e0ae 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs @@ -200,7 +200,7 @@ protected override void ProcessRecord() { WriteObject(result); } - + continue; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs index 3fc313fe222..3f91d597e57 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs @@ -475,7 +475,7 @@ private object InstantiateWebServiceProxy(Assembly assembly) break; } - if (proxyType != null) + if (proxyType != null) { break; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index 0ff52372864..fa49c33d2ab 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -959,7 +959,7 @@ internal static IList BuildPropertyNames(PSObject source, IList /// Converted string. internal string ConvertPropertyNamesCSV(IList propertyNames) { - ArgumentNullException.ThrowIfNull(propertyNames); + ArgumentNullException.ThrowIfNull(propertyNames); _outputString.Clear(); bool first = true; @@ -994,7 +994,7 @@ internal string ConvertPropertyNamesCSV(IList propertyNames) AppendStringWithEscapeAlways(_outputString, propertyName); break; case BaseCsvWritingCommand.QuoteKind.AsNeeded: - + if (propertyName.AsSpan().IndexOfAny(_delimiter, '\n', '"') != -1) { AppendStringWithEscapeAlways(_outputString, propertyName); @@ -1023,7 +1023,7 @@ internal string ConvertPropertyNamesCSV(IList propertyNames) /// internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyNames) { - ArgumentNullException.ThrowIfNull(propertyNames); + ArgumentNullException.ThrowIfNull(propertyNames); _outputString.Clear(); bool first = true; @@ -1109,7 +1109,7 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN /// ToString() value. internal static string GetToStringValueForProperty(PSPropertyInfo property) { - ArgumentNullException.ThrowIfNull(property); + ArgumentNullException.ThrowIfNull(property); string value = null; try @@ -1271,7 +1271,7 @@ internal sealed class ImportCsvHelper internal ImportCsvHelper(PSCmdlet cmdlet, char delimiter, IList header, string typeName, StreamReader streamReader) { - ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(cmdlet); ArgumentNullException.ThrowIfNull(streamReader); _cmdlet = cmdlet; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs index 1f43670531d..c4e423ffd1d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs @@ -158,7 +158,7 @@ protected override void ProcessRecord() ThrowTerminatingError( new ErrorRecord(nse, "CannotDefineSupportedCommand", ErrorCategory.PermissionDenied, null)); } - + SystemPolicy.LogWDACAuditMessage( context: Context, title: ImportLocalizedDataStrings.WDACLogTitle, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs index cf483b16a93..1e741758270 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -824,7 +824,7 @@ protected override void EndProcessing() string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound"; WritePropertyNotFoundError(propertyName, errorId); } - + continue; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs index f2c31faf400..0ea312f2963 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs @@ -196,12 +196,12 @@ protected override void BeginProcessing() { ThrowTerminatingError( new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), + new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateTypeConstrainedLanguage", ErrorCategory.PermissionDenied, targetObject: null)); } - + SystemPolicy.LogWDACAuditMessage( context: Context, title: NewObjectStrings.TypeWDACLogTitle, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs index c384b2a2578..0e466f86d3f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs @@ -45,7 +45,7 @@ protected override void ProcessRecord() { ErrorRecord error = new(ex, "StringNotRecognizedAsGuid", ErrorCategory.InvalidArgument, null); WriteError(error); - } + } } else { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index df1c8b76fb8..839a0b7c051 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -110,7 +110,7 @@ protected override void ProcessRecord() case "Milliseconds": sleepTime = Milliseconds; break; - + case "FromTimeSpan": if (Duration.TotalMilliseconds > int.MaxValue) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs index 05eb8ed96b6..903ff4d8f80 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs @@ -46,6 +46,6 @@ static HttpVersionCompletionsAttribute() /// public HttpVersionCompletionsAttribute() : base(AllowedVersions) { - } + } } } 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 eb8c3e3cc08..173d999b06d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs @@ -98,7 +98,7 @@ protected virtual void Dispose(bool disposing) _cancellationSource.Dispose(); } } - + private readonly List _inputObjects = new(); /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs index c7400e75f9d..c8fed859771 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs @@ -31,7 +31,7 @@ public bool Equals(WebProxy? other) return false; } - // _proxyAddress cannot be null as it is set in the constructor + // _proxyAddress cannot be null as it is set in the constructor return other._credentials == _credentials && _proxyAddress.Equals(other._proxyAddress) && BypassProxyOnLocal == other.BypassProxyOnLocal; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs index 2bf7045cbed..1eadd4934b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs @@ -330,7 +330,7 @@ internal TracePipelineWriter( bool writeError, Collection matchingSources) { - ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(cmdlet); ArgumentNullException.ThrowIfNull(matchingSources); _cmdlet = cmdlet; diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs index e23f0810ea1..4789bcef06f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs @@ -4,18 +4,18 @@ namespace Microsoft.PowerShell { /// - /// HRESULT Wrapper - /// + /// HRESULT Wrapper + /// internal enum HResult { - /// - /// S_OK - /// + /// + /// S_OK + /// Ok = 0x0000, /// /// S_FALSE. - /// + /// False = 0x0001, /// diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs index 38924f3e397..354c61fb8f3 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs @@ -15,7 +15,7 @@ namespace Microsoft.PowerShell /// /// Executor wraps a Pipeline instance, and provides helper methods for executing commands in that pipeline. It is used to /// provide bookkeeping and structure to the use of pipeline in such a way that they can be interrupted and cancelled by a - /// break event handler, and to track nesting of pipelines (which happens with interrupted input loops (aka subshells) and + /// break event handler, and to track nesting of pipelines (which happens with interrupted input loops (aka subshells) and /// use of tab-completion in prompts). The bookkeeping is necessary because the break handler is static and global, and /// there is no means for tying a break handler to an instance of an object. /// diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs index 71e90854dc4..093f7c147dc 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs @@ -25,7 +25,7 @@ protected override { return; } - + try { string outFilename = Host.UI.StopTranscribing(); diff --git a/src/Microsoft.WSMan.Management/CredSSP.cs b/src/Microsoft.WSMan.Management/CredSSP.cs index 03c71f0edb1..a8457ab5bc2 100644 --- a/src/Microsoft.WSMan.Management/CredSSP.cs +++ b/src/Microsoft.WSMan.Management/CredSSP.cs @@ -514,7 +514,7 @@ private void EnableClientSideSettings() try { XmlDocument xmldoc = new XmlDocument(); - + // push the xml string with credssp enabled xmldoc.LoadXml(m_SessionObj.Put(helper.CredSSP_RUri, newxmlcontent, 0)); diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs index 6160577beb2..417f1931c8a 100644 --- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs +++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs @@ -3252,7 +3252,7 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou string tempSchemaFilepath = schemaFiles.FirstOrDefault(); Debug.Assert(schemaFiles.Count() == 1, "A valid DSCResource module can have only one schema mof file"); - + if (tempSchemaFilepath is not null) { var classes = GetCachedClassByFileName(tempSchemaFilepath) ?? ImportClasses(tempSchemaFilepath, new Tuple(module.Name, module.Version), errors); diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs index a1e34424950..2ae4ac2626e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs @@ -20,7 +20,7 @@ namespace System.Management.Automation.Runspaces /// /// This exception is used by Formattable constructor to indicate errors /// occurred during construction time. - /// + /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FormatTable")] public class FormatTableLoadException : RuntimeException { diff --git a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs index b8d0e88905c..dd1c6023cdb 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs @@ -760,7 +760,7 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl { WriteXsiType(@"CmdletParameterMetadataForGetCmdletFilteringParameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); } - + if (o.@IsMandatorySpecified) { WriteAttribute(@"IsMandatory", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@IsMandatory))); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index e4d8203a63b..8aafa939d84 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -1503,7 +1503,7 @@ private static bool TryGetInferredCompletionsForAssignment(Ast expression, Compl { return false; } - + if (inferredTypes.Count == 0) { return false; @@ -2613,7 +2613,7 @@ private static List CompleteLoopLabel(CompletionContext comple return result; } - + private static List CompleteUsingKeywords(int cursorOffset, Token[] tokens, ref int replacementIndex, ref int replacementLength) { var result = new List(); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 16b0d1edac4..80ba4545ff4 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -448,12 +448,12 @@ internal static List CompleteModuleName(CompletionContext cont // eg: Host finds Microsoft.PowerShell.Host // If the user has entered a manual wildcard, or a module name that contains a "." we assume they only want results that matches the input exactly. bool shortNameSearch = wordToComplete.Length > 0 && !WildcardPattern.ContainsWildcardCharacters(wordToComplete) && !wordToComplete.Contains('.'); - + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } - + string[] moduleNames; WildcardPattern shortNamePattern; if (shortNameSearch) @@ -848,7 +848,7 @@ private static bool TryGetParameterHelpMessage( [NotNullWhen(true)] out string? message) { message = null; - + if (attr.HelpMessage is not null) { message = attr.HelpMessage; @@ -926,7 +926,7 @@ private static List GetParameterCompletionResults( addCommonParameters = false; break; } - + if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) { helpMessage = $" - {attrHelpMessage}"; @@ -4772,7 +4772,7 @@ private static List GetFileSystemProviderResults( var resultType = isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem; - + bool leafQuotesNeeded; var completionText = NewPathCompletionText( basePath, @@ -4996,7 +4996,7 @@ private static string RebuildPathWithVars( for (int i = 0; i < path.Length; i++) { // on Windows, we need to preserve the expanded home path as native commands don't understand it -#if UNIX +#if UNIX if (i == homeIndex) { _ = sb.Append('~'); @@ -8628,7 +8628,7 @@ internal static string CombineVariableWithPartialPath(VariableExpressionAst vari /// The parameters to add. /// Collection of command info objects. internal static Collection GetCommandInfo( - IDictionary fakeBoundParameters, + IDictionary fakeBoundParameters, params string[] parametersToAdd) { using var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs index d54f4d5dc87..edf31e8e97a 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs @@ -129,7 +129,7 @@ internal static string NormalizeToExpandableString(string value) /// true if the value matches the word as a wildcard pattern; otherwise, false. /// /// - /// Wildcard pattern matching allows for flexible matching, where wilcards can represent + /// Wildcard pattern matching allows for flexible matching, where wilcards can represent /// multiple characters in the input. This strategy is case-insensitive. /// internal static readonly MatchStrategy WildcardPatternMatchIgnoreCase = (value, wordToComplete) @@ -141,11 +141,11 @@ internal static string NormalizeToExpandableString(string value) /// Determines if the given value matches the specified word considering wildcard characters literally. /// /// - /// true if the value matches either the literal normalized word or the wildcard pattern with escaping; + /// true if the value matches either the literal normalized word or the wildcard pattern with escaping; /// otherwise, false. /// /// - /// This strategy first attempts a literal prefix match for performance and, if unsuccessful, escapes the word to complete to + /// This strategy first attempts a literal prefix match for performance and, if unsuccessful, escapes the word to complete to /// handle any problematic wildcard characters before performing a wildcard match. /// internal static readonly MatchStrategy WildcardPatternEscapeMatch = (value, wordToComplete) diff --git a/src/System.Management.Automation/engine/CommandPathSearch.cs b/src/System.Management.Automation/engine/CommandPathSearch.cs index 92a533e6ec0..12c494ad8ea 100644 --- a/src/System.Management.Automation/engine/CommandPathSearch.cs +++ b/src/System.Management.Automation/engine/CommandPathSearch.cs @@ -111,7 +111,7 @@ private void ResolveCurrentDirectoryInLookupPaths() sessionState.IsProviderLoaded(fileSystemProviderName); string? environmentCurrentDirectory = null; - + try { environmentCurrentDirectory = Directory.GetCurrentDirectory(); diff --git a/src/System.Management.Automation/engine/CommandSearcher.cs b/src/System.Management.Automation/engine/CommandSearcher.cs index 1d945094004..638ccd4ac79 100644 --- a/src/System.Management.Automation/engine/CommandSearcher.cs +++ b/src/System.Management.Automation/engine/CommandSearcher.cs @@ -951,7 +951,7 @@ private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInf if (result != null) { - var formatString = result switch + var formatString = result switch { FilterInfo => "Filter found: {0}", ConfigurationInfo => "Configuration found: {0}", diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index e72a34cef1c..1c5de607321 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -2050,7 +2050,7 @@ public override bool Equals(object obj) { return obj is PSEventSubscriber es && Equals(es); } - + /// /// Determines if two PSEventSubscriber instances are equal /// diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index 2ecf19ccaa9..10e8334021b 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -1694,7 +1694,7 @@ private static PSObject GetParameterType(Type parameterType) } /// - /// Provides argument completion for Noun parameter. + /// Provides argument completion for Noun parameter. /// public class NounArgumentCompleter : IArgumentCompleter { diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index fa9e68f5c24..13d8f66e5e9 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -2943,9 +2943,9 @@ private static object ConvertStringToInteger( } if (resultType == typeof(BigInteger)) - { + { NumberStyles style = NumberStyles.Integer | NumberStyles.AllowThousands; - + return BigInteger.Parse(strToConvert, style, NumberFormatInfo.InvariantInfo); } // Fallback conversion for regular numeric types. diff --git a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs index ca48c5c698c..39c3e9c905e 100644 --- a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs @@ -604,7 +604,7 @@ public IEnumerable CompleteArgument( string parameterName, string wordToComplete, CommandAst commandAst, - IDictionary fakeBoundParameters) + IDictionary fakeBoundParameters) => CompletionHelpers.GetMatchingResults(wordToComplete, possibleCompletionValues: Utils.AllowedEditionValues); } } diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index f07ddcc6335..0a1d0bfc04f 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -6168,7 +6168,7 @@ private void CheckForDisallowedDotSourcing( } } } - + private static void RemoveNestedModuleFunctions( ExecutionContext context, PSModuleInfo module, @@ -7276,7 +7276,7 @@ private static void ImportFunctions(FunctionInfo func, SessionStateInternal targ targetSessionState.ExecutionContext); // Note that the module 'func' and the function table 'functionInfo' instances are now linked - // together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also + // together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also // sets it on the other. SetCommandVisibility(isImportModulePrivate, functionInfo); functionInfo.Module = sourceModule; diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index 0124b01332c..4115546a758 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -500,7 +500,7 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, afterPrev -= arrayExtent.StartOffset; beforeNext -= arrayExtent.StartOffset; - if (arrayText[afterPrev] == ',') + if (arrayText[afterPrev] == ',') { return ", "; } @@ -509,7 +509,7 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, { return " ,"; } - + return " , "; } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index e093210f960..985d90ab3ec 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -2084,8 +2084,8 @@ private void SetPendingBreakpoints(FunctionContext functionContext) else { tuple.Item1.Add(breakpoint.SequencePointIndex, new List { breakpoint }); - } - + } + // We need to keep track of any breakpoints that are bound in each script because they may // need to be rebound if the script changes. var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2; @@ -2099,7 +2099,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext) } // Here could check if all breakpoints for the current functionContext were bound, but because there is no atomic - // api for conditional removal we either need to lock, or do some trickery that has possibility of race conditions. + // api for conditional removal we either need to lock, or do some trickery that has possibility of race conditions. // Instead we keep the item in the dictionary with 0 breakpoint count. This should not be a big issue, // because it is single entry per file that had breakpoints, so there won't be thousands of files in a session. } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 5dcc43ce2a7..822dc552204 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -1557,11 +1557,11 @@ public PSDataCollection ErrorRecords /// /// Serialization information. /// Streaming context. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RunspaceOpenModuleLoadException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization } diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index c56225f3bb7..56bdabbff14 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -952,7 +952,7 @@ private Runspace GetRunspace(int taskId) iss.LanguageMode = PSLanguageMode.FullLanguage; break; } - + runspace = RunspaceFactory.CreateRunspace(iss); runspace.Name = runspaceName; _activeRunspaces.TryAdd(runspace.Id, runspace); diff --git a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs index 66ebf7d0287..f7bca01fe7b 100644 --- a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs @@ -85,7 +85,7 @@ internal InvalidPipelineStateException(string message, PipelineState currentStat /// The that contains contextual information /// about the source or destination. /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] private InvalidPipelineStateException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs index 7073437d7e9..c86b86b4ffb 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs @@ -96,7 +96,7 @@ internal InvalidPowerShellStateException(PSInvocationState currentState) /// The that contains contextual information /// about the source or destination. /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidPowerShellStateException(SerializationInfo info, StreamingContext context) { @@ -3736,7 +3736,7 @@ public PSDataCollection EndInvoke(IAsyncResult asyncResult) /// /// /// When used with , that call will return a partial result. - /// When used with , that call will throw a . + /// When used with , that call will throw a . /// public void Stop() { @@ -3796,7 +3796,7 @@ public IAsyncResult BeginStop(AsyncCallback callback, object state) /// /// /// When used with , that call will return a partial result. - /// When used with , that call will throw a . + /// When used with , that call will throw a . /// public void EndStop(IAsyncResult asyncResult) { @@ -3850,7 +3850,7 @@ public void EndStop(IAsyncResult asyncResult) /// /// /// When used with , that call will return a partial result. - /// When used with , that call will throw a . + /// When used with , that call will throw a . /// public Task StopAsync(AsyncCallback callback, object state) => Task.Factory.FromAsync(BeginStop(callback, state), _endStopMethod); diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs index 5b03ecffa58..7dc9e8d31de 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs @@ -90,7 +90,7 @@ RunspacePoolState expectedState /// The that contains /// contextual information about the source or destination. /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidRunspacePoolStateException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs index 6df0aa74695..1b20fc4c85f 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs @@ -812,7 +812,7 @@ public void ReleaseRunspace(Runspace runspace) EnqueueCheckAndStartRequestServicingThread(null, false); } } - + /// /// Release all resources. /// diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index cead035f057..d69f3bdb88f 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -335,7 +335,7 @@ internal static bool ContainsRangeWildcard(string pattern) foundStart = true; continue; } - + if (foundStart && pattern[index] is ']') { result = true; @@ -524,7 +524,7 @@ public WildcardPatternException(string message, /// /// Serialization information. /// Streaming context. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected WildcardPatternException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs index d0b7ef20680..defd87662e8 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -327,8 +327,8 @@ internal static Dictionary GetUsingValuesForEachParallel( bool isTrustedInput, ExecutionContext context) { - // Using variables for Foreach-Object -Parallel use are restricted to be within the - // Foreach-Object -Parallel call scope. This will filter the using variable map to variables + // Using variables for Foreach-Object -Parallel use are restricted to be within the + // Foreach-Object -Parallel call scope. This will filter the using variable map to variables // only within the current (outer) Foreach-Object -Parallel call scope. var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(scriptBlock.Ast).ToList(); UsingExpressionAst usingAst = null; @@ -358,11 +358,11 @@ internal static Dictionary GetUsingValuesForEachParallel( if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal)) { throw InterpreterError.NewInterpreterException( - targetObject: null, + targetObject: null, exceptionType: typeof(RuntimeException), - errorPosition: usingAst.Extent, + errorPosition: usingAst.Extent, resourceIdAndErrorId: "UsingVariableIsUndefined", - resourceString: AutomationExceptions.UsingVariableIsUndefined, + resourceString: AutomationExceptions.UsingVariableIsUndefined, args: rte.ErrorRecord.TargetObject); } } @@ -435,7 +435,7 @@ private static bool IsInForeachParallelCallingScope( /* Example: $Test1 = "Hello" - 1 | ForEach-Object -Parallel { + 1 | ForEach-Object -Parallel { $using:Test1 $Test2 = "Goodbye" 1 | ForEach-Object -Parallel { @@ -450,7 +450,7 @@ private static bool IsInForeachParallelCallingScope( while (currentParent != scriptblockAst) { // Look for Foreach-Object outer commands - if (currentParent is CommandAst commandAst && + if (currentParent is CommandAst commandAst && FindForEachInCommand(commandAst)) { // Using Ast is outside the invoking foreach scope. diff --git a/src/System.Management.Automation/namespaces/RegistryProvider.cs b/src/System.Management.Automation/namespaces/RegistryProvider.cs index db901160910..4e3b84716cd 100644 --- a/src/System.Management.Automation/namespaces/RegistryProvider.cs +++ b/src/System.Management.Automation/namespaces/RegistryProvider.cs @@ -1835,7 +1835,7 @@ public void GetProperty( WriteError(new ErrorRecord( invalidCast, invalidCast.GetType().FullName, - ErrorCategory.ReadError, + ErrorCategory.ReadError, path)); } } diff --git a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs index f0e61f01a9e..71c81611440 100644 --- a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs +++ b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs @@ -171,7 +171,7 @@ public override string Message /// /// Serialization information. /// Streaming context. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSSnapInException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs index 6c2380023ca..89580932359 100644 --- a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs +++ b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs @@ -78,7 +78,7 @@ public CommandNotFoundException(string message, Exception innerException) : base /// /// streaming context /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CommandNotFoundException(SerializationInfo info, StreamingContext context) { @@ -336,13 +336,13 @@ public ScriptRequiresException(string message, Exception innerException) : base( /// /// streaming context /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptRequiresException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } - + #endregion Serialization #region Properties diff --git a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs index f63ff69bd2c..668c95a25e6 100644 --- a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs +++ b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs @@ -24,7 +24,7 @@ public class ApplicationFailedException : RuntimeException /// The serialization information to use when initializing this object. /// The streaming context to use when initializing this object. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ApplicationFailedException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/ExecutionExceptions.cs b/src/System.Management.Automation/utils/ExecutionExceptions.cs index 69a58d326b2..ff41e045bbb 100644 --- a/src/System.Management.Automation/utils/ExecutionExceptions.cs +++ b/src/System.Management.Automation/utils/ExecutionExceptions.cs @@ -114,12 +114,12 @@ public CmdletInvocationException(string message, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CmdletInvocationException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization #endregion ctor @@ -154,7 +154,7 @@ public override ErrorRecord ErrorRecord /// . /// This is generally reported from the standard provider navigation cmdlets /// such as get-childitem. - /// + /// public class CmdletProviderInvocationException : CmdletInvocationException { #region ctor @@ -193,7 +193,7 @@ public CmdletProviderInvocationException() /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CmdletProviderInvocationException(SerializationInfo info, StreamingContext context) { @@ -281,7 +281,7 @@ private static Exception GetInnerException(Exception e) /// Catching this exception is optional; if the cmdlet or providers chooses not to /// handle PipelineStoppedException and instead allow it to propagate to the /// PowerShell Engine's call to ProcessRecord, the PowerShell Engine will handle it properly. - /// + /// public class PipelineStoppedException : RuntimeException { #region ctor @@ -304,7 +304,7 @@ public PipelineStoppedException() /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineStoppedException(SerializationInfo info, StreamingContext context) { @@ -342,7 +342,7 @@ public PipelineStoppedException(string message, /// to an asynchronous pipeline source and the pipeline has already /// been stopped. /// - /// + /// public class PipelineClosedException : RuntimeException { #region ctor @@ -387,7 +387,7 @@ public PipelineClosedException(string message, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineClosedException(SerializationInfo info, StreamingContext context) { @@ -405,7 +405,7 @@ protected PipelineClosedException(SerializationInfo info, /// /// For example, if $WarningPreference is "Stop", the command will fail with /// this error if a cmdlet calls WriteWarning. - /// + /// public class ActionPreferenceStopException : RuntimeException { #region ctor @@ -467,12 +467,12 @@ internal ActionPreferenceStopException(InvocationInfo invocationInfo, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ActionPreferenceStopException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization /// @@ -612,7 +612,7 @@ public ParentContainsErrorRecordException(string message, /// Streaming context. /// Doesn't return. /// Always. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParentContainsErrorRecordException( SerializationInfo info, StreamingContext context) { @@ -648,7 +648,7 @@ public override string Message /// The redirected object is available as /// /// in the ErrorRecord which contains this exception. - /// + /// public class RedirectedException : RuntimeException { #region constructors @@ -697,7 +697,7 @@ public RedirectedException(string message, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RedirectedException(SerializationInfo info, StreamingContext context) { @@ -719,7 +719,7 @@ protected RedirectedException(SerializationInfo info, /// call depth to prevent stack overflows. The maximum call depth is configurable /// but generally high enough that scripts which are not deeply recursive /// should not have a problem. - /// + /// public class ScriptCallDepthException : SystemException, IContainsErrorRecord { #region ctor @@ -765,7 +765,7 @@ public ScriptCallDepthException(string message, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptCallDepthException(SerializationInfo info, StreamingContext context) { @@ -860,11 +860,11 @@ public PipelineDepthException(string message, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineDepthException(SerializationInfo info, - StreamingContext context) + StreamingContext context) { - throw new NotSupportedException(); + throw new NotSupportedException(); } #endregion Serialization @@ -918,7 +918,7 @@ public int CallDepth /// /// Note that HaltCommandException does not define IContainsErrorRecord. /// This is because it is not reported to the user. - /// + /// public class HaltCommandException : SystemException { #region ctor @@ -963,7 +963,7 @@ public HaltCommandException(string message, /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HaltCommandException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs index 362a1448207..df36d248d18 100644 --- a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs +++ b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs @@ -100,7 +100,7 @@ class HostException : RuntimeException /// /// The contextual information about the source or destination. /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HostException(SerializationInfo info, StreamingContext context) { @@ -120,7 +120,7 @@ private void SetDefaultErrorRecord() /// /// Defines the exception thrown when an error occurs from prompting for a command parameter. - /// + /// public class PromptingException : HostException { @@ -209,7 +209,7 @@ class PromptingException : HostException /// /// The contextual information about the source or destination. /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PromptingException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/MshArgumentException.cs b/src/System.Management.Automation/utils/MshArgumentException.cs index baa3f8c3163..452527d1c18 100644 --- a/src/System.Management.Automation/utils/MshArgumentException.cs +++ b/src/System.Management.Automation/utils/MshArgumentException.cs @@ -68,7 +68,7 @@ public PSArgumentException(string message, string paramName) /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/MshArgumentNullException.cs b/src/System.Management.Automation/utils/MshArgumentNullException.cs index a4f50256e44..16ef442e5ec 100644 --- a/src/System.Management.Automation/utils/MshArgumentNullException.cs +++ b/src/System.Management.Automation/utils/MshArgumentNullException.cs @@ -79,12 +79,12 @@ public PSArgumentNullException(string paramName, string message) /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentNullException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization #endregion ctor diff --git a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs index 8d666778a4f..4e65ed0acb1 100644 --- a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs +++ b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs @@ -67,13 +67,13 @@ public PSArgumentOutOfRangeException(string paramName, object actualValue, strin /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentOutOfRangeException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } - + #endregion Serialization /// diff --git a/src/System.Management.Automation/utils/MshInvalidOperationException.cs b/src/System.Management.Automation/utils/MshInvalidOperationException.cs index f398e4a2e99..8400e762360 100644 --- a/src/System.Management.Automation/utils/MshInvalidOperationException.cs +++ b/src/System.Management.Automation/utils/MshInvalidOperationException.cs @@ -38,7 +38,7 @@ public PSInvalidOperationException() /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSInvalidOperationException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/MshNotImplementedException.cs b/src/System.Management.Automation/utils/MshNotImplementedException.cs index 7d1082bf747..1b925053410 100644 --- a/src/System.Management.Automation/utils/MshNotImplementedException.cs +++ b/src/System.Management.Automation/utils/MshNotImplementedException.cs @@ -38,12 +38,12 @@ public PSNotImplementedException() /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSNotImplementedException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization /// diff --git a/src/System.Management.Automation/utils/MshNotSupportedException.cs b/src/System.Management.Automation/utils/MshNotSupportedException.cs index 1268636742b..a1e519a960e 100644 --- a/src/System.Management.Automation/utils/MshNotSupportedException.cs +++ b/src/System.Management.Automation/utils/MshNotSupportedException.cs @@ -38,13 +38,13 @@ public PSNotSupportedException() /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSNotSupportedException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } - + #endregion Serialization /// diff --git a/src/System.Management.Automation/utils/ParameterBinderExceptions.cs b/src/System.Management.Automation/utils/ParameterBinderExceptions.cs index 4052eec0b81..a28de586d8e 100644 --- a/src/System.Management.Automation/utils/ParameterBinderExceptions.cs +++ b/src/System.Management.Automation/utils/ParameterBinderExceptions.cs @@ -297,7 +297,7 @@ internal ParameterBindingException( /// /// streaming context /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingException( SerializationInfo info, StreamingContext context) @@ -486,7 +486,7 @@ private string BuildMessage() #endregion Private } - + internal class ParameterBindingValidationException : ParameterBindingException { #region Preferred constructors @@ -654,7 +654,7 @@ internal ParameterBindingValidationException( /// /// streaming context /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingValidationException( SerializationInfo info, StreamingContext context) @@ -844,7 +844,7 @@ internal ParameterBindingArgumentTransformationException( /// /// streaming context /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingArgumentTransformationException( SerializationInfo info, StreamingContext context) @@ -854,7 +854,7 @@ protected ParameterBindingArgumentTransformationException( #endregion serialization } - + internal class ParameterBindingParameterDefaultValueException : ParameterBindingException { #region Preferred constructors @@ -1018,7 +1018,7 @@ internal ParameterBindingParameterDefaultValueException( /// /// streaming context /// - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingParameterDefaultValueException( SerializationInfo info, StreamingContext context) diff --git a/src/System.Management.Automation/utils/RuntimeException.cs b/src/System.Management.Automation/utils/RuntimeException.cs index 61a3a345eff..ab013359d5c 100644 --- a/src/System.Management.Automation/utils/RuntimeException.cs +++ b/src/System.Management.Automation/utils/RuntimeException.cs @@ -40,12 +40,12 @@ public RuntimeException() /// Serialization information. /// Streaming context. /// Constructed object. - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RuntimeException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization /// diff --git a/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs b/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs index d9d082879a6..8bd2ae3e620 100644 --- a/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs +++ b/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs @@ -21,7 +21,7 @@ internal class EtwActivityReverterMethodInvoker : public EtwActivityReverterMethodInvoker(IEtwEventCorrelator eventCorrelator) { - ArgumentNullException.ThrowIfNull(eventCorrelator); + ArgumentNullException.ThrowIfNull(eventCorrelator); _eventCorrelator = eventCorrelator; _invoker = DoInvoke; diff --git a/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs b/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs index f90bd92c6ed..625ba88a23b 100755 --- a/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs +++ b/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs @@ -132,7 +132,7 @@ internal override void LogCommandLifecycleEvent(Func getLogContext, } else { - // When state is Start log the CommandLine which has arguments for completeness. + // When state is Start log the CommandLine which has arguments for completeness. payload.AppendLine(StringUtil.Format(EtwLoggingStrings.CommandStateChange, logContext.CommandLine, newState.ToString())); } } diff --git a/test/perf/benchmarks/Engine.Compiler.cs b/test/perf/benchmarks/Engine.Compiler.cs index 4f9dbab88a9..68385847f5b 100644 --- a/test/perf/benchmarks/Engine.Compiler.cs +++ b/test/perf/benchmarks/Engine.Compiler.cs @@ -61,7 +61,7 @@ public void GlobalSetup() // believe that there is no need to run many ops in each iteration. However, the subsequent runs // of this method is much faster than the first run, and this causes 'MinIterationTime' warnings // to our benchmarks and make the benchmark results not reliable. - // Calling this method once in 'GlobalSetup' is a workaround. + // Calling this method once in 'GlobalSetup' is a workaround. // See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157 CompileFunction(); } diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs index f478b6f7378..9bc477bc4fe 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs @@ -13,7 +13,7 @@ public static class SummaryExtensions public static int ToExitCode(this IEnumerable summaries) { // an empty summary means that initial filtering and validation did not allow to run - if (!summaries.Any()) + if (!summaries.Any()) return 1; // if anything has failed, it's an error diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs index a2837d61988..d7089f6f5a8 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs @@ -10,13 +10,13 @@ public class PartitionFilter : IFilter private readonly int? _partitionsCount; private readonly int? _partitionIndex; // indexed from 0 private int _counter = 0; - + public PartitionFilter(int? partitionCount, int? partitionIndex) { _partitionsCount = partitionCount; _partitionIndex = partitionIndex; } - + public bool Predicate(BenchmarkCase benchmarkCase) { if (!_partitionsCount.HasValue || !_partitionIndex.HasValue) diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs index 47d5564309e..cd9c3a424ce 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs @@ -14,9 +14,9 @@ namespace BenchmarkDotNet.Extensions public class TooManyTestCasesValidator : IValidator { private const int Limit = 16; - + public static readonly IValidator FailOnError = new TooManyTestCasesValidator(); - + public bool TreatsWarningsAsErrors => true; public IEnumerable Validate(ValidationParameters validationParameters) diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs index 7bfab8445cb..0309e1e9065 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs @@ -30,7 +30,7 @@ private class BenchmarkArgumentsComparer : IEqualityComparer { public bool Equals(BenchmarkCase x, BenchmarkCase y) => Enumerable.SequenceEqual( - x.Parameters.Items.Select(argument => argument.Value), + x.Parameters.Items.Select(argument => argument.Value), y.Parameters.Items.Select(argument => argument.Value)); public int GetHashCode(BenchmarkCase obj) diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs index 7e2b587abca..85f5d98af59 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs @@ -33,7 +33,7 @@ public static T[] ArrayOfUniqueValues(int count) // of random-sized memory by BDN engine T[] result = new T[count]; - var random = new Random(Seed); + var random = new Random(Seed); var uniqueValues = new HashSet(); @@ -49,12 +49,12 @@ public static T[] ArrayOfUniqueValues(int count) return result; } - + public static T[] Array(int count) { var result = new T[count]; - var random = new Random(Seed); + var random = new Random(Seed); if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) { diff --git a/test/perf/dotnet-tools/Reporting/Reporter.cs b/test/perf/dotnet-tools/Reporting/Reporter.cs index 9291d6f36eb..d99ecfaf47c 100644 --- a/test/perf/dotnet-tools/Reporting/Reporter.cs +++ b/test/perf/dotnet-tools/Reporting/Reporter.cs @@ -44,7 +44,7 @@ public static Reporter CreateReporter(IEnvironment environment = null) { ret.Init(); } - + return ret; } @@ -66,7 +66,7 @@ private void Init() { var split = kvp.Split('='); run.Configurations.Add(split[0], split[1]); - } + } } os = new Os() @@ -91,7 +91,7 @@ private void Init() public string GetJson() { if (!InLab) - { + { return null; } var jsonobj = new @@ -122,7 +122,7 @@ public string WriteResultTable() ret.AppendLine($"{LeftJustify("Metric", counterWidth)}|{LeftJustify("Average",resultWidth)}|{LeftJustify("Min", resultWidth)}|{LeftJustify("Max",resultWidth)}"); ret.AppendLine($"{new String('-', counterWidth)}|{new String('-', resultWidth)}|{new String('-', resultWidth)}|{new String('-', resultWidth)}"); - + ret.AppendLine(Print(defaultCounter, counterWidth, resultWidth)); foreach(var counter in topCounters) { diff --git a/test/perf/dotnet-tools/ResultsComparer/Program.cs b/test/perf/dotnet-tools/ResultsComparer/Program.cs index 68615f61c78..a0c14e0057a 100644 --- a/test/perf/dotnet-tools/ResultsComparer/Program.cs +++ b/test/perf/dotnet-tools/ResultsComparer/Program.cs @@ -191,7 +191,7 @@ private static void ExportToCsv((string id, Benchmark baseResult, Benchmark diff private static void ExportToXml((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)[] notSame, FileInfo xmlPath) { if (xmlPath == null) - { + { Console.WriteLine("No file given"); return; } diff --git a/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs b/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs index 219c01058e9..73bb24ccd08 100644 --- a/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs +++ b/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs @@ -151,7 +151,7 @@ public class SaveMyFileCommand : PSCmdlet [Parameter] public string FileName { get; set; } - + [Experimental("ExpTest.FeatureOne", ExperimentAction.Show)] [Parameter] public string Destination { get; set; } diff --git a/test/xUnit/csharp/test_CryptoUtils.cs b/test/xUnit/csharp/test_CryptoUtils.cs index 0f737a94260..6531924d437 100644 --- a/test/xUnit/csharp/test_CryptoUtils.cs +++ b/test/xUnit/csharp/test_CryptoUtils.cs @@ -22,7 +22,7 @@ public static void TestSessionKeyExchange() string publicKey = cryptoClient.GetPublicKeyAsBase64EncodedString(); cryptoServer.ImportPublicKeyFromBase64EncodedString(publicKey); // sent to and imported by server - // generate, export, import session key + // generate, export, import session key cryptoServer.GenerateSessionKey(); // server provides the session key? string sessionKey = cryptoServer.SafeExportSessionKey(); cryptoClient.ImportSessionKeyFromBase64EncodedString(sessionKey); diff --git a/test/xUnit/csharp/test_FileSystemProvider.cs b/test/xUnit/csharp/test_FileSystemProvider.cs index 1bea0f84ce4..06cb43e6088 100644 --- a/test/xUnit/csharp/test_FileSystemProvider.cs +++ b/test/xUnit/csharp/test_FileSystemProvider.cs @@ -43,7 +43,7 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); - } + } protected virtual void Dispose(bool disposing) { diff --git a/test/xUnit/csharp/test_PSConfiguration.cs b/test/xUnit/csharp/test_PSConfiguration.cs index 54e69da68f0..de94107fc6b 100644 --- a/test/xUnit/csharp/test_PSConfiguration.cs +++ b/test/xUnit/csharp/test_PSConfiguration.cs @@ -99,7 +99,7 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); - } + } protected virtual void Dispose(bool disposing) { From 9461d96d559e525c201f649af28a4783519bc9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nagy-Egri=20M=C3=A1t=C3=A9=20Ferenc?= Date: Thu, 16 Oct 2025 10:29:49 +0200 Subject: [PATCH 030/255] Dynamically evaluate width of LastWriteTime for formatting output on Unix (#24624) --- .../DefaultFormatters/FileSystem_format_ps1xml.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs index 0400f99b899..4c8d23971af 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Globalization; namespace System.Management.Automation.Runspaces { @@ -48,7 +49,10 @@ private static IEnumerable ViewsOf_FileSystemTypes(CustomC .AddHeader(Alignment.Left, label: "UnixMode", width: 10) .AddHeader(Alignment.Right, label: "User", width: 10) .AddHeader(Alignment.Left, label: "Group", width: 10) - .AddHeader(Alignment.Right, label: "LastWriteTime", width: 16) + .AddHeader( + Alignment.Right, + label: "LastWriteTime", + width: String.Format(CultureInfo.CurrentCulture, "{0:d} {0:HH}:{0:mm}", CultureInfo.CurrentCulture.Calendar.MaxSupportedDateTime).Length) .AddHeader(Alignment.Right, label: "Size", width: 12) .AddHeader(Alignment.Left, label: "Name") .StartRowDefinition(wrap: true) From b46e0709fbf7c1b4b9b572fc2bd6889faca12cd8 Mon Sep 17 00:00:00 2001 From: Elvin Aslanov Date: Thu, 16 Oct 2025 13:29:11 +0200 Subject: [PATCH 031/255] Capitalize Windows in PSNativeWindowsTildeExpansion experimental feature description (#25266) --- .../engine/ExperimentalFeature/ExperimentalFeature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index dd26e609641..6ff7e1d6820 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -118,7 +118,7 @@ static ExperimentalFeature() description: "Replace the hard-coded suggestion framework with the extensible feedback provider"), new ExperimentalFeature( name: PSNativeWindowsTildeExpansion, - description: "On windows, expand unquoted tilde (`~`) with the user's current home folder."), + description: "On Windows, expand unquoted tilde (`~`) with the user's current home folder."), new ExperimentalFeature( name: PSRedirectToVariable, description: "Add support for redirecting to the variable drive"), From 1d14c25d6bd3280a8639dd3e134ddb582010bae6 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 16 Oct 2025 13:44:30 -0700 Subject: [PATCH 032/255] Update changelog for v7.5.4 and v7.4.13 (#26202) --- CHANGELOG/7.4.md | 32 ++++++++++++++++++++++++++++++++ CHANGELOG/7.5.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/CHANGELOG/7.4.md b/CHANGELOG/7.4.md index a4966b09e99..89a3f64e37c 100644 --- a/CHANGELOG/7.4.md +++ b/CHANGELOG/7.4.md @@ -1,5 +1,37 @@ # 7.4 Changelog +## [7.4.13] + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.415

+ +
+ +
    +
  • [release/v7.4] Update StableRelease to not be the latest (#26042)
  • +
  • [release/v7.4] Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26033)
  • +
  • [release/v7.4] Add 7.4.12 Changelog (#26018)
  • +
  • [release/v7.4] Fix variable reference for release environment in pipeline (#26014)
  • +
  • Backport Release Pipeline Changes (Internal 37169)
  • +
  • [release/v7.4] Update branch for release (#26194)
  • +
  • [release/v7.4] Mark the 3 consistently failing tests as pending to unblock PRs (#26197)
  • +
  • [release/v7.4] Remove UseDotnet task and use the dotnet-install script (#26170)
  • +
  • [release/v7.4] Automate Store Publishing (#26163)
  • +
  • [release/v7.4] add CodeQL suppresion for NativeCommandProcessor (#26174)
  • +
  • [release/v7.4] add CodeQL suppressions for UpdatableHelp and NativeCommandProcessor methods (#26172)
  • +
  • [release/v7.4] Suppress false positive PSScriptAnalyzer warnings in tests and build scripts (#26058)
  • +
  • [release/v7.4] Ensure that socket timeouts are set only during the token validation (#26080)
  • +
+ +
+ +[7.4.13]: https://github.com/PowerShell/PowerShell/compare/v7.4.12...v7.4.13 + ## [7.4.12] ### Tools diff --git a/CHANGELOG/7.5.md b/CHANGELOG/7.5.md index e231ce6b0e2..80b3ee0a8aa 100644 --- a/CHANGELOG/7.5.md +++ b/CHANGELOG/7.5.md @@ -1,5 +1,38 @@ # 7.5 Changelog +## [7.5.4] + +### Build and Packaging Improvements + +
+ + + +

Update to .NET SDK 9.0.306

+ +
+ +
    +
  • [release/v7.5] Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26032)
  • +
  • [release/v7.5] Fix variable reference for release environment in pipeline (#26013)
  • +
  • [release/v7.5] Add v7.5.3 Changelog (#26015)
  • +
  • [release/v7.5] Add LinuxHost Network configuration to PowerShell Packages pipeline (#26002)
  • +
  • Backport Release Pipeline Changes (Internal 37168)
  • +
  • [release/v7.5] Update branch for release (#26195)
  • +
  • [release/v7.5] Mark the 3 consistently failing tests as pending to unblock PRs (#26196)
  • +
  • [release/v7.5] add CodeQL suppresion for NativeCommandProcessor (#26173)
  • +
  • [release/v7.5] add CodeQL suppressions for UpdatableHelp and NativeCommandProcessor methods (#26171)
  • +
  • [release/v7.5] Remove UseDotnet task and use the dotnet-install script (#26169)
  • +
  • [release/v7.5] Automate Store Publishing (#26164)
  • +
  • [release/v7.5] Ensure that socket timeouts are set only during the token validation (#26079)
  • +
  • [release/v7.5] Suppress false positive PSScriptAnalyzer warnings in tests and build scripts (#26059)
  • +
+ +
+ +[7.5.4]: https://github.com/PowerShell/PowerShell/compare/v7.5.3...v7.5.4 + + ## [7.5.3] ### General Cmdlet Updates and Fixes From b4f42a07e065b7da486dce053d47b6149c2dc66c Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 17 Oct 2025 05:38:05 +0100 Subject: [PATCH 033/255] Replace stackallocs with collection expressions (#25803) --- .../host/msh/ConsoleHostUserInterface.cs | 2 +- .../host/msh/ConsoleTextWriter.cs | 4 +- .../CoreCLR/CorePsPlatform.cs | 40 ++++++++++--------- .../engine/Interop/Windows/QueryDosDevice.cs | 2 +- .../Interop/Windows/WNetGetConnection.cs | 2 +- .../namespaces/FileSystemProvider.cs | 11 ++--- src/powershell/Program.cs | 14 ++----- 7 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 51fe8203d80..f0da0d99547 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -578,7 +578,7 @@ private static bool shouldUnsetMode( [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteToConsole(char c, bool transcribeResult) { - ReadOnlySpan value = stackalloc char[1] { c }; + ReadOnlySpan value = [c]; WriteToConsole(value, transcribeResult); } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs index 97a02cb7123..a82156ce1c8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs @@ -76,8 +76,8 @@ public override void Write(char c) { - ReadOnlySpan c1 = stackalloc char[1] { c }; - _ui.WriteToConsole(c1, transcribeResult: true); + ReadOnlySpan value = [c]; + _ui.WriteToConsole(value, transcribeResult: true); } public override diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index dc5db5f2c48..f4a7622b66f 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -637,25 +637,29 @@ public string GetModeString() return DirectoryOwnerFullGroupReadExecOtherReadExec; } - Span modeCharacters = stackalloc char[10]; - modeCharacters[0] = itemTypeTable[ItemType]; - bool isExecutable; - UnixFileMode modeInfo = (UnixFileMode)Mode; - modeCharacters[1] = modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm; - modeCharacters[2] = modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm; - isExecutable = modeInfo.HasFlag(UnixFileMode.UserExecute); - modeCharacters[3] = modeInfo.HasFlag(UnixFileMode.SetUser) ? (isExecutable ? SetAndExec : SetAndNotExec) : (isExecutable ? CanExecute : NoPerm); - - modeCharacters[4] = modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm; - modeCharacters[5] = modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm; - isExecutable = modeInfo.HasFlag(UnixFileMode.GroupExecute); - modeCharacters[6] = modeInfo.HasFlag(UnixFileMode.SetGroup) ? (isExecutable ? SetAndExec : SetAndNotExec) : (isExecutable ? CanExecute : NoPerm); - - modeCharacters[7] = modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm; - modeCharacters[8] = modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm; - isExecutable = modeInfo.HasFlag(UnixFileMode.OtherExecute); - modeCharacters[9] = modeInfo.HasFlag(UnixFileMode.StickyBit) ? (isExecutable ? StickyAndExec : StickyAndNotExec) : (isExecutable ? CanExecute : NoPerm); + + Span modeCharacters = [ + itemTypeTable[ItemType], + + modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.SetUser) ? + (modeInfo.HasFlag(UnixFileMode.UserExecute) ? SetAndExec : SetAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.UserExecute) ? CanExecute : NoPerm), + + modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.SetGroup) ? + (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? SetAndExec : SetAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? CanExecute : NoPerm), + + modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.StickyBit) ? + (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? StickyAndExec : StickyAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? CanExecute : NoPerm), + ]; return new string(modeCharacters); } diff --git a/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs b/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs index 27907bd0135..d23899e8ef7 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs @@ -28,7 +28,7 @@ internal static string GetDosDeviceForNetworkPath(char deviceName) #endif Span buffer = stackalloc char[StartLength + 1]; - Span fullDeviceName = stackalloc char[3] { deviceName, ':', '\0' }; + Span fullDeviceName = [deviceName, ':', '\0']; char[]? rentedArray = null; try diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs index 88ec4386a14..01667fb4274 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs @@ -25,7 +25,7 @@ internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) return ERROR_NOT_SUPPORTED; } - ReadOnlySpan driveName = stackalloc char[] { drive, ':', '\0' }; + ReadOnlySpan driveName = [drive, ':', '\0']; int bufferSize = MAX_PATH; Span uncBuffer = stackalloc char[MAX_PATH]; if (InternalTestHooks.WNetGetConnectionBufferSize > 0 && InternalTestHooks.WNetGetConnectionBufferSize <= MAX_PATH) diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 56c8a074334..71de4e8ac67 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -1903,15 +1903,16 @@ string ToModeString(FileSystemInfo fileSystemInfo) } } - bool isDirectory = fileAttributes.HasFlag(FileAttributes.Directory); - ReadOnlySpan mode = stackalloc char[] - { - isLink ? 'l' : isDirectory ? 'd' : '-', + ReadOnlySpan mode = + [ + isLink ? + 'l' : + fileAttributes.HasFlag(FileAttributes.Directory) ? 'd' : '-', fileAttributes.HasFlag(FileAttributes.Archive) ? 'a' : '-', fileAttributes.HasFlag(FileAttributes.ReadOnly) ? 'r' : '-', fileAttributes.HasFlag(FileAttributes.Hidden) ? 'h' : '-', fileAttributes.HasFlag(FileAttributes.System) ? 's' : '-', - }; + ]; return new string(mode); } diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index 70346a1d1ce..aa79bb8cf07 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -129,10 +129,7 @@ private static void AttemptExecPwshLogin(string[] args) // At this point, we are on macOS // Set up the mib array and the query for process maximum args size - Span mib = stackalloc int[3]; - int mibLength = 2; - mib[0] = MACOS_CTL_KERN; - mib[1] = MACOS_KERN_ARGMAX; + Span mib = [MACOS_CTL_KERN, MACOS_KERN_ARGMAX]; int size = IntPtr.Size / 2; int argmax = 0; @@ -141,7 +138,7 @@ private static void AttemptExecPwshLogin(string[] args) { fixed (int *mibptr = mib) { - ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mibLength, &argmax, &size, IntPtr.Zero, 0)); + ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mib.Length, &argmax, &size, IntPtr.Zero, 0)); } } @@ -155,16 +152,13 @@ private static void AttemptExecPwshLogin(string[] args) IntPtr executablePathPtr = IntPtr.Zero; try { - mib[0] = MACOS_CTL_KERN; - mib[1] = MACOS_KERN_PROCARGS2; - mib[2] = pid; - mibLength = 3; + mib = [MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid]; unsafe { fixed (int *mibptr = mib) { - ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mibLength, procargs.ToPointer(), &argmax, IntPtr.Zero, 0)); + ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mib.Length, procargs.ToPointer(), &argmax, IntPtr.Zero, 0)); } // The memory block we're reading is a series of null-terminated strings From 907c3b1f62ede096b1b0e36b9f34b59f44000373 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:30:27 +0100 Subject: [PATCH 034/255] Fix `IDE0049` for `object` in `System.Management.Automation`. Part 1 (#25923) --- .../engine/Attributes.cs | 6 ++-- .../engine/ErrorPackage.cs | 4 +-- .../engine/ProgressRecord.cs | 2 +- .../hostifaces/MshHostRawUserInterface.cs | 30 +++++++++---------- .../namespaces/ContainerProviderBase.cs | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 31c520498c0..dac3a2ac377 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -87,7 +87,7 @@ namespace System.Management.Automation /// validates the argument as a whole. If the argument value may /// be an enumerable, you can derive from /// which will take care of unrolling the enumerable and validate each element individually. - /// It is also recommended to override to return a readable string + /// It is also recommended to override to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, the string[] command argument will be validated. @@ -154,7 +154,7 @@ protected ValidateArgumentsAttribute() /// and override the /// /// abstract method, after which they can apply the attribute to their parameters. - /// It is also recommended to override to return a readable string + /// It is also recommended to override to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, each string command argument will be validated. @@ -2292,7 +2292,7 @@ public ValidateNotNullOrWhiteSpaceAttribute() /// and override the /// abstract method, after which they /// can apply the attribute to their parameters. - /// It is also recommended to override to return a readable + /// It is also recommended to override to return a readable /// string similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If multiple transformations are defined on a parameter, they will be invoked in series, /// each getting the output of the previous transformation. diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index baa2dd215de..2a4e152c098 100644 --- a/src/System.Management.Automation/engine/ErrorPackage.cs +++ b/src/System.Management.Automation/engine/ErrorPackage.cs @@ -796,7 +796,7 @@ internal Exception TextLookupError #region ToString /// - /// As + /// As /// /// Developer-readable identifier. public override string ToString() @@ -1677,7 +1677,7 @@ private string GetInvocationTypeName() #region ToString /// - /// As + /// As /// /// Developer-readable identifier. public override string ToString() diff --git a/src/System.Management.Automation/engine/ProgressRecord.cs b/src/System.Management.Automation/engine/ProgressRecord.cs index 56d5518dcf8..b3139dc8e6e 100644 --- a/src/System.Management.Automation/engine/ProgressRecord.cs +++ b/src/System.Management.Automation/engine/ProgressRecord.cs @@ -293,7 +293,7 @@ internal ProgressRecord(ProgressRecord other) } /// - /// Overrides + /// Overrides /// /// /// "parent = a id = b act = c stat = d cur = e pct = f sec = g type = h" where diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs index 201caf3c827..64b200b7d61 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs @@ -62,7 +62,7 @@ public int Y } /// - /// Overrides + /// Overrides /// /// /// "a,b" where a and b are the values of the X and Y properties. @@ -75,7 +75,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -99,7 +99,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -248,7 +248,7 @@ public int Height } /// - /// Overloads + /// Overloads /// /// /// "a,b" where a and b are the values of the Width and Height properties. @@ -261,7 +261,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -285,7 +285,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -557,7 +557,7 @@ bool keyDown } /// - /// Overloads + /// Overloads /// /// /// "a,b,c,d" where a, b, c, and d are the values of the VirtualKeyCode, Character, ControlKeyState, and KeyDown properties. @@ -569,7 +569,7 @@ public override return string.Create(CultureInfo.InvariantCulture, $"{VirtualKeyCode},{Character},{ControlKeyState},{KeyDown}"); } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -593,7 +593,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -787,7 +787,7 @@ public int Bottom } /// - /// Overloads + /// Overloads /// /// /// "a,b ; c,d" where a, b, c, and d are values of the Left, Top, Right, and Bottom properties. @@ -800,7 +800,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -824,7 +824,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -1015,7 +1015,7 @@ public BufferCellType BufferCellType } /// - /// Overloads + /// Overloads /// /// /// "'a' b c d" where a, b, c, and d are the values of the Character, ForegroundColor, BackgroundColor, and Type properties. @@ -1028,7 +1028,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -1052,7 +1052,7 @@ public override } /// - /// Overrides + /// Overrides /// /// diff --git a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs index cc3e0ac83e2..2a600097108 100644 --- a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs @@ -840,7 +840,7 @@ protected virtual object RenameItemDynamicParameters(string path, string newName /// /// The parameter can be any type of object that the provider can use /// to create the item. It is recommended that the provider accept at a minimum strings, and an instance - /// of the type of object that would be returned from GetItem() for this path. + /// of the type of object that would be returned from GetItem() for this path. /// can be used to convert some types to the desired type. /// /// The default implementation of this method throws an . From 135bb540901834979dfbfb0e5bddcca81e61975c Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:31:10 +0100 Subject: [PATCH 035/255] Fix `IDE0049` for `string` in `System.Management.Automation` (#25921) --- .../commands/management/ComputerUnix.cs | 2 +- .../cimSupport/cmdletization/QueryBuilder.cs | 4 ++-- .../engine/ErrorPackage.cs | 14 +++++++------- .../engine/hostifaces/FieldDescription.cs | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs index eb286f7c190..22092116330 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs @@ -158,7 +158,7 @@ protected override void StopProcessing() /// /// Run shutdown command. /// - protected void RunShutdown(String args) + protected void RunShutdown(string args) { if (shutdownPath is null) { diff --git a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs index 36f781d98a0..e71e4c7c04f 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs @@ -52,7 +52,7 @@ public abstract class QueryBuilder /// Property name to query on. /// Property values to accept in the query. /// - /// if should be treated as a containing a wildcard pattern; + /// if should be treated as a containing a wildcard pattern; /// otherwise. /// /// @@ -69,7 +69,7 @@ public virtual void FilterByProperty(string propertyName, IEnumerable allowedPro /// Property name to query on. /// Property values to reject in the query. /// - /// if should be treated as a containing a wildcard pattern; + /// if should be treated as a containing a wildcard pattern; /// otherwise. /// /// diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index 2a4e152c098..6dfaf34fbec 100644 --- a/src/System.Management.Automation/engine/ErrorPackage.cs +++ b/src/System.Management.Automation/engine/ErrorPackage.cs @@ -562,7 +562,7 @@ public ErrorDetails(string message) /// /// /// - /// + /// /// insertion parameters /// /// @@ -584,7 +584,7 @@ public ErrorDetails(string message) /// by overriding virtual method /// . /// This constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( Cmdlet cmdlet, @@ -610,7 +610,7 @@ public ErrorDetails( /// /// /// - /// + /// /// insertion parameters /// /// @@ -637,7 +637,7 @@ public ErrorDetails( /// will implement /// . /// The constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( IResourceSupplier resourceSupplier, @@ -663,7 +663,7 @@ public ErrorDetails( /// /// /// - /// + /// /// insertion parameters /// /// @@ -678,7 +678,7 @@ public ErrorDetails( /// This constructor first loads a template string from the assembly using /// . /// The constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( System.Reflection.Assembly assembly, @@ -1823,7 +1823,7 @@ public interface IResourceSupplier /// if you want more complex behavior. /// /// Insertions will be inserted into the string with - /// + /// /// to generate the final error message in /// . /// diff --git a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs index 541b1607df0..3f47fedb55e 100644 --- a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs @@ -89,7 +89,7 @@ public string Name /// /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// /// @@ -115,7 +115,7 @@ public string Name ///
/// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// /// @@ -144,7 +144,7 @@ public string Name /// to load the containing assembly to access the type information. AssemblyName is used for this purpose. /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// public string From 23a6cd329ae809ab1f663a5ea30b85ef7526baef Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:30:58 +0100 Subject: [PATCH 036/255] Remove nightly build status section from README.md (#26227) --- README.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README.md b/README.md index dade0207250..7874646c063 100644 --- a/README.md +++ b/README.md @@ -61,21 +61,6 @@ There are dozens of topic-specific channels on our community-driven PowerShell V * [IRC](https://web.libera.chat/#powershell) on Libera.Chat * [Slack](https://aka.ms/psslack) -### Build status of nightly builds - -| Azure CI (Windows) | Azure CI (Linux) | Azure CI (macOS) | CodeFactor Grade | -|:-----------------------------------------|:-----------------------------------------------|:-----------------------------------------------|:-------------------------| -| [![windows-nightly-image][]][windows-nightly-site] | [![linux-nightly-image][]][linux-nightly-site] | [![macOS-nightly-image][]][macos-nightly-site] | [![cf-image][]][cf-site] | - -[windows-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=32 -[linux-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=23 -[macos-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=24 -[windows-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-Windows-daily -[linux-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-linux-daily?branchName=master -[macOS-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-macos-daily?branchName=master -[cf-site]: https://www.codefactor.io/repository/github/powershell/powershell -[cf-image]: https://www.codefactor.io/repository/github/powershell/powershell/badge - ## Developing and Contributing Want to contribute to PowerShell? Please start with the [Contribution Guide][] to learn how to develop and contribute. From 8472a37f26353779c1a3afd8cefbc6f581686d94 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:31:37 +0100 Subject: [PATCH 037/255] Remove Gitter from README (#26200) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7874646c063..a7b31c475f8 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ Want to chat with other members of the PowerShell community? There are dozens of topic-specific channels on our community-driven PowerShell Virtual User Group, which you can join on: -* [Gitter](https://gitter.im/PowerShell/PowerShell) * [Discord](https://discord.gg/PowerShell) * [IRC](https://web.libera.chat/#powershell) on Libera.Chat * [Slack](https://aka.ms/psslack) From 2786fceb3ab35b553b82f1067cddcc540d3e15ca Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:06:45 +0100 Subject: [PATCH 038/255] Fix IDE0083: UseNotPattern (#26213) --- .../engine/remoting/client/ClientMethodExecutor.cs | 2 +- .../engine/remoting/client/Job.cs | 10 +++++----- .../engine/remoting/client/JobSourceAdapter.cs | 4 ++-- .../remoting/client/RemoteRunspacePoolInternal.cs | 2 +- .../engine/remoting/client/remoterunspaceinfo.cs | 2 +- .../engine/remoting/commands/CustomShellCommands.cs | 2 +- .../engine/remoting/commands/InvokeCommandCommand.cs | 2 +- .../engine/remoting/commands/ReceivePSSession.cs | 2 +- .../engine/remoting/common/RunspaceConnectionInfo.cs | 2 +- .../remoting/common/WireDataFormat/EncodeAndDecode.cs | 4 ++-- .../remoting/fanin/InitialSessionStateProvider.cs | 2 +- .../engine/remoting/server/ServerRemoteHost.cs | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs index 73c432158f3..9d52bcb4dc9 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs @@ -133,7 +133,7 @@ internal static void Dispatch( ///
private static bool IsRunspacePushed(PSHost host) { - if (!(host is IHostSupportsInteractiveSession host2)) + if (host is not IHostSupportsInteractiveSession host2) { return false; } diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index b3a0135afc7..adf47d04cab 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -752,10 +752,10 @@ private void WriteError(Cmdlet cmdlet, ErrorRecord errorRecord) private static Exception GetExceptionFromErrorRecord(ErrorRecord errorRecord) { - if (!(errorRecord.Exception is RuntimeException runtimeException)) + if (errorRecord.Exception is not RuntimeException runtimeException) return null; - if (!(runtimeException is RemoteException remoteException)) + if (runtimeException is not RemoteException remoteException) return null; PSPropertyInfo wasThrownFromThrow = @@ -1911,7 +1911,7 @@ internal List GetJobsForComputer(string computerName) foreach (Job j in ChildJobs) { - if (!(j is PSRemotingChildJob child)) continue; + if (j is not PSRemotingChildJob child) continue; if (string.Equals(child.Runspace.ConnectionInfo.ComputerName, computerName, StringComparison.OrdinalIgnoreCase)) { @@ -1934,7 +1934,7 @@ internal List GetJobsForRunspace(PSSession runspace) foreach (Job j in ChildJobs) { - if (!(j is PSRemotingChildJob child)) continue; + if (j is not PSRemotingChildJob child) continue; if (child.Runspace.InstanceId.Equals(runspace.InstanceId)) { returnJobList.Add(child); @@ -1957,7 +1957,7 @@ internal List GetJobsForOperation(IThrottleOperation operation) foreach (Job j in ChildJobs) { - if (!(j is PSRemotingChildJob child)) continue; + if (j is not PSRemotingChildJob child) continue; if (child.Helper.Equals(helper)) { returnJobList.Add(child); diff --git a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs index 9f6297fbfab..85cd6c7ba1f 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs @@ -413,7 +413,7 @@ public void StoreJobIdForReuse(Job2 job, bool recurse) duplicateDetector.Add(job.InstanceId, job.InstanceId); foreach (Job child in job.ChildJobs) { - if (!(child is Job2 childJob)) continue; + if (child is not Job2 childJob) continue; StoreJobIdForReuseHelper(duplicateDetector, childJob, true); } } @@ -429,7 +429,7 @@ private void StoreJobIdForReuseHelper(Hashtable duplicateDetector, Job2 job, boo if (!recurse || job.ChildJobs == null) return; foreach (Job child in job.ChildJobs) { - if (!(child is Job2 childJob)) continue; + if (child is not Job2 childJob) continue; StoreJobIdForReuseHelper(duplicateDetector, childJob, recurse); } } diff --git a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs index c414b5095e6..983180bd5ca 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs @@ -1237,7 +1237,7 @@ public override RunspacePoolCapability GetCapabilities() internal static RunspacePool[] GetRemoteRunspacePools(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) { - if (!(connectionInfo is WSManConnectionInfo wsmanConnectionInfoParam)) + if (connectionInfo is not WSManConnectionInfo wsmanConnectionInfoParam) { // Disconnect-Connect currently only supported by WSMan. throw new NotSupportedException(); diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs index 55c2b23db75..c1b34691104 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs @@ -374,7 +374,7 @@ public static PSSession Create( string transportName, PSCmdlet psCmdlet) { - if (!(runspace is RemoteRunspace remoteRunspace)) + if (runspace is not RemoteRunspace remoteRunspace) { throw new PSArgumentException(RemotingErrorIdStrings.InvalidPSSessionArgument); } diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index e600661eab9..1d88210e7b6 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -1753,7 +1753,7 @@ internal static string CreateConditionalACEFromConfig( } StringBuilder conditionalACE = new StringBuilder(); - if (!(configTable[ConfigFileConstants.RequiredGroups] is Hashtable requiredGroupsHash)) + if (configTable[ConfigFileConstants.RequiredGroups] is not Hashtable requiredGroupsHash) { throw new PSInvalidOperationException(RemotingErrorIdStrings.RequiredGroupsNotHashTable); } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 2145a1dbe2a..0dd769537c6 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -1050,7 +1050,7 @@ protected override void BeginProcessing() // create collection of input writers here foreach (IThrottleOperation operation in Operations) { - if (!(operation is ExecutionCmdletHelperRunspace ecHelper)) + if (operation is not ExecutionCmdletHelperRunspace ecHelper) { // either all the operations will be of type ExecutionCmdletHelperRunspace // or not...there is no mix. diff --git a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs index b9cf33e4b1c..67477e5b36f 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs @@ -1166,7 +1166,7 @@ private static PSSession ConnectSession(PSSession session, out Exception ex) /// PSSession disconnected runspace object. private PSSession TryGetSessionFromServer(PSSession session) { - if (!(session.Runspace is RemoteRunspace remoteRunspace)) + if (session.Runspace is not RemoteRunspace remoteRunspace) { return null; } diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index d9a6d0b317b..5f25851337e 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -1174,7 +1174,7 @@ private static string ResolveShellUri(string shell) internal static T ExtractPropertyAsWsManConnectionInfo(RunspaceConnectionInfo rsCI, string property, T defaultValue) { - if (!(rsCI is WSManConnectionInfo wsCI)) + if (rsCI is not WSManConnectionInfo wsCI) { return defaultValue; } diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs index c7b06d4c74d..e26f3421cda 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs @@ -1869,7 +1869,7 @@ internal static object GetPowerShellOutput(object data) /// PSInvocationInfo. internal static PSInvocationStateInfo GetPowerShellStateInfo(object data) { - if (!(data is PSObject dataAsPSObject)) + if (data is not PSObject dataAsPSObject) { throw new PSRemotingDataStructureException( RemotingErrorIdStrings.DecodingErrorForPowerShellStateInfo); @@ -2137,7 +2137,7 @@ internal static RemoteStreamOptions GetRemoteStreamOptions(object data) /// RemoteSessionCapability object. internal static RemoteSessionCapability GetSessionCapability(object data) { - if (!(data is PSObject dataAsPSObject)) + if (data is not PSObject dataAsPSObject) { throw new PSRemotingDataStructureException( RemotingErrorIdStrings.CantCastRemotingDataToPSObject, data.GetType().FullName); diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 94ffe3a68e6..07e17e8b63a 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -2836,7 +2836,7 @@ internal static Hashtable[] TryGetHashtableArray(object hashObj) for (int i = 0; i < hashArray.Length; i++) { - if (!(objArray[i] is Hashtable hash)) + if (objArray[i] is not Hashtable hash) { return null; } diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs index 7e7aa767d5c..71dda0d0b7a 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs @@ -327,7 +327,7 @@ public override void PushRunspace(Runspace runspace) throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostAlreadyPushed); } - if (!(runspace is RemoteRunspace remoteRunspace)) + if (runspace is not RemoteRunspace remoteRunspace) { throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostNotRemoteRunspace); } From 0d5d7deeff2f53d05732a2f751d7b5618cabc4d5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:15:13 -0700 Subject: [PATCH 039/255] Integrate Windows packaging into windows-ci workflow using reusable workflow (#26224) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk --- .../infrastructure/path-filters/action.yml | 18 ++++ .github/workflows/windows-ci.yml | 10 +++ .../workflows/windows-packaging-reusable.yml | 88 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 .github/workflows/windows-packaging-reusable.yml diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml index e150b1a9d09..09ed7c22d17 100644 --- a/.github/actions/infrastructure/path-filters/action.yml +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -26,6 +26,9 @@ outputs: buildModuleChanged: description: 'Build module changes' value: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: + description: 'Packaging related changes' + value: ${{ steps.filter.outputs.packagingChanged }} runs: using: composite steps: @@ -85,6 +88,19 @@ runs: const globalConfigChanged = files.some(file => file.filename.startsWith('.globalconfig')) || files.some(file => file.filename.startsWith('nuget.config')) || files.some(file => file.filename.startsWith('global.json')); + const packagingChanged = files.some(file => + file.filename === '.github/workflows/windows-ci.yml' || + file.filename.startsWith('assets/wix/') || + file.filename === 'PowerShell.Common.props' || + file.filename.match(/^src\/.*\.csproj$/) || + file.filename.startsWith('test/packaging/windows/') || + file.filename.startsWith('tools/packaging/') || + file.filename.startsWith('tools/wix/') + ) || + buildModuleChanged || + globalConfigChanged || + toolsCiPsm1Changed; + const source = mainSourceChanged || toolsChanged || githubChanged || propsChanged || testsChanged || globalConfigChanged; core.setOutput('toolsChanged', toolsChanged); @@ -94,6 +110,7 @@ runs: core.setOutput('mainSourceChanged', mainSourceChanged); core.setOutput('buildModuleChanged', buildModuleChanged); core.setOutput('globalConfigChanged', globalConfigChanged); + core.setOutput('packagingChanged', packagingChanged); core.setOutput('source', source); @@ -106,4 +123,5 @@ runs: Write-Verbose -Verbose "tests: ${{ steps.filter.outputs.testsChanged }}" Write-Verbose -Verbose "mainSource: ${{ steps.filter.outputs.mainSourceChanged }}" Write-Verbose -Verbose "buildModule: ${{ steps.filter.outputs.buildModuleChanged }}" + Write-Verbose -Verbose "packaging: ${{ steps.filter.outputs.packagingChanged }}" shell: pwsh diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 63e6c5ab7a3..64d37eb1ae5 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -41,6 +41,8 @@ env: POWERSHELL_TELEMETRY_OPTOUT: 1 __SuppressAnsiEscapeSequences: 1 nugetMultiFeedWarnLevel: none + SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts jobs: changes: name: Change Detection @@ -54,6 +56,7 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout uses: actions/checkout@v5 @@ -158,6 +161,12 @@ jobs: fetch-depth: 1000 - name: Verify xUnit test results uses: "./.github/actions/test/verify_xunit" + windows_packaging: + name: Windows Packaging + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + uses: ./.github/workflows/windows-packaging-reusable.yml ready_to_merge: name: windows ready to merge needs: @@ -166,6 +175,7 @@ jobs: - windows_test_elevated_others - windows_test_unelevated_ci - windows_test_unelevated_others + - windows_packaging if: always() uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 with: diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml new file mode 100644 index 00000000000..5a763544c62 --- /dev/null +++ b/.github/workflows/windows-packaging-reusable.yml @@ -0,0 +1,88 @@ +name: Windows Packaging (Reusable) + +on: + workflow_call: + +env: + GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts + +jobs: + package: + name: ${{ matrix.architecture }} - ${{ matrix.channel }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - architecture: x64 + channel: preview + runtimePrefix: win7 + - architecture: x86 + channel: stable + runtimePrefix: win7 + - architecture: x86 + channel: preview + runtimePrefix: win7 + - architecture: arm64 + channel: preview + runtimePrefix: win + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1000 + + - name: Capture Environment + if: success() || failure() + run: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + shell: pwsh + + - name: Capture PowerShell Version Table + if: success() || failure() + run: | + $PSVersionTable + shell: pwsh + + - name: Switch to Public Feeds + if: success() + run: | + Import-Module .\tools\ci.psm1 + Switch-PSNugetConfig -Source Public + shell: pwsh + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json + + - name: Bootstrap + if: success() + run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + shell: pwsh + + - name: Build and Package + run: | + Import-Module .\tools\ci.psm1 + New-CodeCoverageAndTestPackage + Invoke-CIFinish -Runtime ${{ matrix.runtimePrefix }}-${{ matrix.architecture }} -channel ${{ matrix.channel }} + shell: pwsh + + - name: Upload Build Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} + path: | + ${{ github.workspace }}/artifacts/**/* + !${{ github.workspace }}/artifacts/**/*.pdb From 1bd038c4df941dde7876b430362f6dcf813d9d5d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:16:22 -0700 Subject: [PATCH 040/255] Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26225) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .../actions/test/linux-packaging/action.yml | 112 +++++++++--------- .github/workflows/linux-ci.yml | 32 +++-- build.psm1 | 2 +- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index b4a9c3b55c0..736bddfa7a7 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -1,12 +1,5 @@ name: linux_packaging -description: 'Test very basic Linux packaging' - -# This isn't working yet -# It fails with - -# ERROR: While executing gem ... (Gem::FilePermissionError) -# You don't have write permissions for the /var/lib/gems/2.7.0 directory. -# WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. +description: 'Linux packaging for PowerShell' runs: using: composite @@ -15,58 +8,64 @@ runs: if: success() || failure() run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' shell: pwsh + + - uses: actions/setup-dotnet@v5 + with: + global-json-file: ./global.json + - name: Download Build Artifacts uses: actions/download-artifact@v4 with: - path: "${{ github.workspace }}" + name: build + path: "${{ runner.workspace }}/build" + - name: Capture Artifacts Directory continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + run: Get-ChildItem "${{ runner.workspace }}/build/*" -Recurse shell: pwsh - name: Bootstrap run: |- Import-Module ./build.psm1 Start-PSBootstrap -Scenario Package + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" shell: pwsh - - name: Capture Artifacts Directory - continue-on-error: true - run: Import-Module ./build.psm1 + + - name: Extract Build ZIP + run: |- + $destinationFolder = "${{ runner.workspace }}/bins" + $archiveFile = "${{ runner.workspace }}/build/build.zip" + + Write-Verbose "Extracting $archiveFile to $destinationFolder" -Verbose + New-Item -ItemType Directory -Path $destinationFolder -Force | Out-Null + Expand-Archive -Path $archiveFile -DestinationPath $destinationFolder -Force shell: pwsh - - name: Extract Files - uses: actions/github-script@v7.0.0 - env: - DESTINATION_FOLDER: "${{ github.workspace }}/bins" - ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" - with: - script: |- - const fs = require('fs').promises - const path = require('path') - const target = path.resolve(process.env.DESTINATION_FOLDER) - const patterns = process.env.ARCHIVE_FILE_PATTERNS - const globber = await glob.create(patterns) - await io.mkdirP(path.dirname(target)) - for await (const file of globber.globGenerator()) { - if ((await fs.lstat(file)).isDirectory()) continue - await exec.exec(`7z x ${file} -o${target} -aoa`) - } + - name: Fix permissions continue-on-error: true run: |- - find "${{ github.workspace }}/bins" -type d -exec chmod +rwx {} \; - find "${{ github.workspace }}/bins" -type f -exec chmod +rw {} \; + find "${{ runner.workspace }}/bins" -type d -exec chmod +rwx {} \; + find "${{ runner.workspace }}/bins" -type f -exec chmod +rw {} \; shell: bash + - name: Capture Extracted Build ZIP continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + run: Get-ChildItem "${{ runner.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue shell: pwsh - - name: Packaging Tests - if: success() + + - name: Create Packages + env: + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ runner.workspace }}/packages run: |- + # Create the artifacts staging directory + New-Item -ItemType Directory -Path "$env:BUILD_ARTIFACTSTAGINGDIRECTORY" -Force | Out-Null + Import-Module ./tools/ci.psm1 - Restore-PSOptions -PSOptionsPath '${{ github.workspace }}/build/psoptions.json' + Restore-PSOptions -PSOptionsPath '${{ runner.workspace }}/build/psoptions.json' $options = (Get-PSOptions) - $rootPath = '${{ github.workspace }}/bins' + $rootPath = '${{ runner.workspace }}/bins' $originalRootPath = Split-Path -path $options.Output $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) $pwshPath = Join-Path -path $path -ChildPath 'pwsh' @@ -75,21 +74,24 @@ runs: Set-PSOptions $options Invoke-CIFinish shell: pwsh - - name: Upload packages - run: |- - Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.deb" -Recurse | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=deb;artifactname=deb]$packagePath" - } - Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.rpm" -Recurse | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" - } - Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.tar.gz" -Recurse | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" - } - shell: pwsh + + - name: Upload deb packages + uses: actions/upload-artifact@v4 + with: + name: packages-deb + path: ${{ runner.workspace }}/packages/*.deb + if-no-files-found: ignore + + - name: Upload rpm packages + uses: actions/upload-artifact@v4 + with: + name: packages-rpm + path: ${{ runner.workspace }}/packages/*.rpm + if-no-files-found: ignore + + - name: Upload tar.gz packages + uses: actions/upload-artifact@v4 + with: + name: packages-tar + path: ${{ runner.workspace }}/packages/*.tar.gz + if-no-files-found: ignore diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index b58b75c64a4..2e4c54d4068 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -229,24 +229,22 @@ jobs: - linux_test_unelevated_ci - linux_test_unelevated_others - analyze + - linux_packaging if: always() uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 with: needs_context: ${{ toJson(needs) }} - # TODO: Enable this when we have a Linux packaging workflow - - # ERROR: While executing gem ... (Gem::FilePermissionError) - # You don't have write permissions for the /var/lib/gems/2.7.0 directory. - # WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. - - # linux_packaging: - # name: Attempt Linux Packaging - # needs: ci_build - # runs-on: ubuntu-20.04 - # steps: - # - name: checkout - # uses: actions/checkout@v5 - # with: - # fetch-depth: 1000 - # - name: Verify xUnit test results - # uses: "./.github/actions/test/linux-packaging" + linux_packaging: + name: Linux Packaging + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Linux Packaging + uses: "./.github/actions/test/linux-packaging" diff --git a/build.psm1 b/build.psm1 index a610c684e2c..98d080ecc99 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2252,7 +2252,7 @@ function Install-GlobalGem { # We cannot guess if the user wants to run gem install as root on linux and windows, # but macOs usually requires sudo $gemsudo = '' - if($environment.IsMacOS -or $env:TF_BUILD) { + if($environment.IsMacOS -or $env:TF_BUILD -or $env:GITHUB_ACTIONS) { $gemsudo = $sudo } From fcb0e5cde36d50c998095264a3d398ec10ba7b54 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 20 Oct 2025 06:18:37 +0100 Subject: [PATCH 041/255] Remove redundant `CharSet` from `StructLayout` attributes. Part 1 (#26216) --- .../namespaces/Win32Native.cs | 4 ++-- .../security/SecureStringHelper.cs | 2 +- .../security/nativeMethods.cs | 16 ++++++++-------- .../utils/PlatformInvokes.cs | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/System.Management.Automation/namespaces/Win32Native.cs b/src/System.Management.Automation/namespaces/Win32Native.cs index 5c668fd471f..4f2c65c49f5 100644 --- a/src/System.Management.Automation/namespaces/Win32Native.cs +++ b/src/System.Management.Automation/namespaces/Win32Native.cs @@ -76,14 +76,14 @@ internal enum SID_NAME_USE #region Struct - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct SID_AND_ATTRIBUTES { internal IntPtr Sid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_USER { internal SID_AND_ATTRIBUTES User; diff --git a/src/System.Management.Automation/security/SecureStringHelper.cs b/src/System.Management.Automation/security/SecureStringHelper.cs index d06e33f9567..5ff5881bab4 100644 --- a/src/System.Management.Automation/security/SecureStringHelper.cs +++ b/src/System.Management.Automation/security/SecureStringHelper.cs @@ -594,7 +594,7 @@ internal static class CAPI internal const int E_FILENOTFOUND = unchecked((int)0x80070002); // File not found internal const int ERROR_FILE_NOT_FOUND = 2; // File not found - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct CRYPTOAPI_BLOB { internal uint cbData; diff --git a/src/System.Management.Automation/security/nativeMethods.cs b/src/System.Management.Automation/security/nativeMethods.cs index ae880e05518..6dd8f59d613 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -1287,28 +1287,28 @@ internal enum SecurityInformation : uint UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000 } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID { internal uint LowPart; internal uint HighPart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATTRIBUTES { internal LUID Luid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGE { internal uint PrivilegeCount; internal LUID_AND_ATTRIBUTES Privilege; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct ACL { internal byte AclRevision; @@ -1318,7 +1318,7 @@ internal struct ACL internal ushort Sbz2; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct ACE_HEADER { internal byte AceType; @@ -1326,7 +1326,7 @@ internal struct ACE_HEADER internal ushort AceSize; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_AUDIT_ACE { internal ACE_HEADER Header; @@ -1334,7 +1334,7 @@ internal struct SYSTEM_AUDIT_ACE internal uint SidStart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LSA_UNICODE_STRING { internal ushort Length; @@ -1342,7 +1342,7 @@ internal struct LSA_UNICODE_STRING internal IntPtr Buffer; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct CENTRAL_ACCESS_POLICY { internal IntPtr CAPID; diff --git a/src/System.Management.Automation/utils/PlatformInvokes.cs b/src/System.Management.Automation/utils/PlatformInvokes.cs index fbd4117e844..f9e78f5d1d3 100644 --- a/src/System.Management.Automation/utils/PlatformInvokes.cs +++ b/src/System.Management.Automation/utils/PlatformInvokes.cs @@ -444,28 +444,28 @@ internal static extern bool AdjustTokenPrivileges(IntPtr tokenHandler, bool disa out TOKEN_PRIVILEGE previousPrivilegeState, ref int returnLength); - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGE { internal uint PrivilegeCount; internal LUID_AND_ATTRIBUTES Privilege; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID { internal uint LowPart; internal uint HighPart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATTRIBUTES { internal LUID Luid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct PRIVILEGE_SET { internal uint PrivilegeCount; From e25ade306baaf9e7db7b4e1df8bce9cf476ff33c Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 20 Oct 2025 07:12:49 +0100 Subject: [PATCH 042/255] Enable IDE0080: RemoveConfusingSuppressionForIsExpression (#26206) --- .globalconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index 68fb0a66b2d..ed1d6c1a335 100644 --- a/.globalconfig +++ b/.globalconfig @@ -1454,7 +1454,7 @@ dotnet_diagnostic.IDE0079.severity = silent # IDE0080: RemoveConfusingSuppressionForIsExpression # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0080 -dotnet_diagnostic.IDE0080.severity = silent +dotnet_diagnostic.IDE0080.severity = warning # IDE0081: RemoveUnnecessaryByVal # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0081 From 4a7bf2574e723ffc86816c5c8d6cdcd1fa1031b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:11:45 -0700 Subject: [PATCH 043/255] Bump github/codeql-action from 4.30.8 to 4.30.9 (#26249) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linux-ci.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 2e4c54d4068..b12ebbcf317 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -192,7 +192,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -218,7 +218,7 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 ready_to_merge: name: Linux ready to merge diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2a12425a829..f612ed0e22a 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 with: sarif_file: results.sarif From b53a551284606e234fa8fbbbff4a06eca9df08dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:12:17 -0700 Subject: [PATCH 044/255] Bump actions/setup-dotnet from 4 to 5 (#26248) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/windows-packaging-reusable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index 5a763544c62..a48e071e7a1 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -60,7 +60,7 @@ jobs: shell: pwsh - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: global-json-file: ./global.json From 9470212e6a1190551a79cacf54c531637f8f1ff0 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 20 Oct 2025 19:16:44 +0100 Subject: [PATCH 045/255] Fix CA1837: Use 'Environment.ProcessId' (#26242) --- .../security/CertificateProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index 2a05a9bf30d..08ee5a70f51 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -3459,8 +3459,7 @@ private static bool IsUIAllowed(PSHost host) return false; uint SessionId; - uint ProcessId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id; - if (!SMASecurity.NativeMethods.ProcessIdToSessionId(ProcessId, out SessionId)) + if (!SMASecurity.NativeMethods.ProcessIdToSessionId((uint)Environment.ProcessId, out SessionId)) return false; if (SessionId == 0) From 5661a5152f0aba7ab460132566d4f36950993096 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:18:23 -0700 Subject: [PATCH 046/255] Replace fpm with native rpmbuild for RPM package generation (#26233) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../actions/test/linux-packaging/action.yml | 13 + .github/workflows/linux-ci.yml | 3 +- build.psm1 | 21 +- .../linux/package-validation.tests.ps1 | 91 +++++ tools/ci.psm1 | 26 +- tools/packaging/packaging.psm1 | 326 ++++++++++++++++-- 6 files changed, 440 insertions(+), 40 deletions(-) create mode 100644 test/packaging/linux/package-validation.tests.ps1 diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index 736bddfa7a7..b7bbdf37185 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -62,6 +62,9 @@ runs: # Create the artifacts staging directory New-Item -ItemType Directory -Path "$env:BUILD_ARTIFACTSTAGINGDIRECTORY" -Force | Out-Null + # Import packaging module to ensure RPM packaging changes are loaded + Import-Module ./build.psm1 -Force + Import-Module ./tools/packaging/packaging.psm1 -Force Import-Module ./tools/ci.psm1 Restore-PSOptions -PSOptionsPath '${{ runner.workspace }}/build/psoptions.json' $options = (Get-PSOptions) @@ -75,6 +78,16 @@ runs: Invoke-CIFinish shell: pwsh + - name: Validate Package Names + run: |- + # Run Pester tests to validate package names + Import-Module Pester -Force + $testResults = Invoke-Pester -Path ./test/packaging/linux/package-validation.tests.ps1 -PassThru + if ($testResults.FailedCount -gt 0) { + throw "Package validation tests failed" + } + shell: pwsh + - name: Upload deb packages uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index b12ebbcf317..0f0cd110303 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -53,6 +53,7 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout uses: actions/checkout@v5 @@ -239,7 +240,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout diff --git a/build.psm1 b/build.psm1 index 98d080ecc99..88878bdd635 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2401,11 +2401,24 @@ function Start-PSBootstrap { } # Install [fpm](https://github.com/jordansissel/fpm) + # Note: fpm is now only needed for DEB and macOS packages; RPM packages use rpmbuild directly if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { - Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1" - Install-GlobalGem -Sudo $sudo -GemName "ffi" -GemVersion "1.16.3" - Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1" - Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5" + # Install fpm on Debian-based systems, macOS, and Mariner (where DEB packages are built) + if (($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) -or $environment.IsMacOS) { + Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1" + Install-GlobalGem -Sudo $sudo -GemName "ffi" -GemVersion "1.16.3" + Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1" + Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5" + } + + # For RPM-based systems, ensure rpmbuild is available + if ($environment.IsLinux -and ($environment.IsRedHatFamily -or $environment.IsSUSEFamily -or $environment.IsMariner)) { + Write-Verbose -Verbose "Checking for rpmbuild..." + if (!(Get-Command rpmbuild -ErrorAction SilentlyContinue)) { + Write-Warning "rpmbuild not found. Installing rpm-build package..." + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y rpm-build")) -IgnoreExitcode + } + } } } diff --git a/test/packaging/linux/package-validation.tests.ps1 b/test/packaging/linux/package-validation.tests.ps1 new file mode 100644 index 00000000000..241863de45d --- /dev/null +++ b/test/packaging/linux/package-validation.tests.ps1 @@ -0,0 +1,91 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Linux Package Name Validation" { + BeforeAll { + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "$env:GITHUB_WORKSPACE/../packages" + } else { + $env:SYSTEM_ARTIFACTSDIRECTORY + } + + if (-not $artifactsDir) { + throw "Artifacts directory not found. GITHUB_WORKSPACE or SYSTEM_ARTIFACTSDIRECTORY must be set." + } + + Write-Verbose "Artifacts directory: $artifactsDir" -Verbose + } + + Context "RPM Package Names" { + It "Should have valid RPM package names" { + $rpmPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.rpm -ErrorAction SilentlyContinue + + if ($rpmPackages.Count -eq 0) { + Set-ItResult -Skipped -Because "No RPM packages found in artifacts directory" + return + } + + $invalidPackages = @() + # Regex pattern for valid RPM package names. + # Breakdown: + # ^powershell\- : Starts with 'powershell-' + # (preview-|lts-)? : Optionally 'preview-' or 'lts-' + # \d+\.\d+\.\d+ : Version number (e.g., 7.6.0) + # (_[a-z]*\.\d+)? : Optional underscore, letters, dot, and digits (e.g., _alpha.1) + # -1\. : Literal '-1.' + # (preview\.\d+\.)? : Optional 'preview.' and digits, followed by a dot + # (rh|cm)\. : Either 'rh.' or 'cm.' + # (x86_64|aarch64)\.rpm$ : Architecture and file extension + $rpmPackageNamePattern = 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1\.(preview\.\d+\.)?(rh|cm)\.(x86_64|aarch64)\.rpm' + + foreach ($package in $rpmPackages) { + if ($package.Name -notmatch $rpmPackageNamePattern) { + $invalidPackages += "$($package.Name) is not a valid RPM package name" + Write-Warning "$($package.Name) is not a valid RPM package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + + $rpmPackages.Count | Should -BeGreaterThan 0 + } + } + + Context "Tar.Gz Package Names" { + It "Should have valid tar.gz package names" { + $tarPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.tar.gz -ErrorAction SilentlyContinue + + if ($tarPackages.Count -eq 0) { + Set-ItResult -Skipped -Because "No tar.gz packages found in artifacts directory" + return + } + + $invalidPackages = @() + foreach ($package in $tarPackages) { + # Pattern matches: powershell-7.6.0-preview.6-linux-x64.tar.gz or powershell-7.6.0-linux-x64.tar.gz + # Also matches various runtime configurations + if ($package.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?(linux|osx|linux-musl)+\-(x64\-fxdependent|x64|arm32|arm64|x64\-musl-noopt\-fxdependent)\.(tar\.gz)') { + $invalidPackages += "$($package.Name) is not a valid tar.gz package name" + Write-Warning "$($package.Name) is not a valid tar.gz package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + + $tarPackages.Count | Should -BeGreaterThan 0 + } + } + + Context "Package Existence" { + It "Should find at least one package in artifacts directory" { + $allPackages = Get-ChildItem -Path $artifactsDir -Recurse -Include *.rpm, *.tar.gz, *.deb -ErrorAction SilentlyContinue + + $allPackages.Count | Should -BeGreaterThan 0 -Because "At least one package should exist in the artifacts directory" + } + } +} diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 7b13ded1811..9e95e68c843 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -874,16 +874,36 @@ function New-LinuxPackage $packageObj = $package } - Write-Log -message "Artifacts directory: ${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" - Copy-Item $packageObj.FullName -Destination "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" -Force + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "${env:GITHUB_WORKSPACE}/../packages" + } else { + "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" + } + + # Ensure artifacts directory exists + if (-not (Test-Path $artifactsDir)) { + New-Item -ItemType Directory -Path $artifactsDir -Force | Out-Null + } + + Write-Log -message "Artifacts directory: $artifactsDir" + Copy-Item $packageObj.FullName -Destination $artifactsDir -Force } if ($IsLinux) { + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "${env:GITHUB_WORKSPACE}/../packages" + } else { + "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" + } + # Create and package Raspbian .tgz + # Build must be clean for Raspbian Start-PSBuild -PSModuleRestore -Clean -Runtime linux-arm -Configuration 'Release' $armPackage = Start-PSPackage @packageParams -Type tar-arm -SkipReleaseChecks - Copy-Item $armPackage -Destination "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" -Force + Copy-Item $armPackage -Destination $artifactsDir -Force } } diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 8e7fb3c4e08..894323a4b98 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1245,40 +1245,124 @@ function New-UnixPackage { # Setup package dependencies $Dependencies = @(Get-PackageDependencies @packageDependenciesParams) - $Arguments = @() - - - $Arguments += Get-FpmArguments ` - -Name $Name ` - -Version $packageVersion ` - -Iteration $Iteration ` - -Description $Description ` - -Type $Type ` - -Dependencies $Dependencies ` - -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` - -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` - -Staging $Staging ` - -Destination $Destination ` - -ManGzipFile $ManGzipInfo.GzipFile ` - -ManDestination $ManGzipInfo.ManFile ` - -LinkInfo $Links ` - -AppsFolder $AppsFolder ` - -Distribution $DebDistro ` - -HostArchitecture $HostArchitecture ` - -ErrorAction Stop - # Build package try { - if ($PSCmdlet.ShouldProcess("Create $type package")) { - Write-Log "Creating package with fpm $Arguments..." - try { - $Output = Start-NativeExecution { fpm $Arguments } + if ($Type -eq 'rpm') { + # Use rpmbuild directly for RPM packages + if ($PSCmdlet.ShouldProcess("Create RPM package with rpmbuild")) { + Write-Log "Creating RPM package with rpmbuild..." + + # Create rpmbuild directory structure + $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" + $specsDir = Join-Path $rpmBuildRoot "SPECS" + $rpmsDir = Join-Path $rpmBuildRoot "RPMS" + + New-Item -ItemType Directory -Path $specsDir -Force | Out-Null + New-Item -ItemType Directory -Path $rpmsDir -Force | Out-Null + + # Generate RPM spec file + $specContent = New-RpmSpec ` + -Name $Name ` + -Version $packageVersion ` + -Iteration $Iteration ` + -Description $Description ` + -Dependencies $Dependencies ` + -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` + -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` + -Staging $Staging ` + -Destination $Destination ` + -ManGzipFile $ManGzipInfo.GzipFile ` + -ManDestination $ManGzipInfo.ManFile ` + -LinkInfo $Links ` + -Distribution $DebDistro ` + -HostArchitecture $HostArchitecture + + $specFile = Join-Path $specsDir "$Name.spec" + $specContent | Out-File -FilePath $specFile -Encoding ascii + Write-Verbose "Generated spec file: $specFile" -Verbose + + # Log the spec file content + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Host "::group::RPM Spec File Content" + Write-Host $specContent + Write-Host "::endgroup::" + } else { + Write-Verbose "RPM Spec File Content:`n$specContent" -Verbose + } + + # Build RPM package + try { + # Use bash to properly handle rpmbuild arguments + # Add --target for cross-architecture builds + $targetArch = "" + if ($HostArchitecture -ne "x86_64" -and $HostArchitecture -ne "noarch") { + $targetArch = "--target $HostArchitecture" + } + $buildCmd = "rpmbuild -bb --quiet $targetArch --define '_topdir $rpmBuildRoot' --buildroot '$rpmBuildRoot/BUILDROOT' '$specFile'" + Write-Verbose "Running: $buildCmd" -Verbose + $Output = bash -c $buildCmd 2>&1 + $exitCode = $LASTEXITCODE + + if ($exitCode -ne 0) { + throw "rpmbuild failed with exit code $exitCode" + } + + # Find the generated RPM + $rpmFile = Get-ChildItem -Path (Join-Path $rpmsDir $HostArchitecture) -Filter "*.rpm" -ErrorAction Stop | + Sort-Object -Property LastWriteTime -Descending | + Select-Object -First 1 + + if ($rpmFile) { + # Copy RPM to current location + Copy-Item -Path $rpmFile.FullName -Destination $CurrentLocation -Force + $Output = @("Created package {:path=>""$($rpmFile.Name)""}") + } else { + throw "RPM file not found after build" + } + } + catch { + Write-Verbose -Message "!!!Handling error in rpmbuild!!!" -Verbose -ErrorAction SilentlyContinue + if ($Output) { + Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue + } + Get-Error -InputObject $_ + throw + } } - catch { - Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue - Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue - Get-Error -InputObject $_ - throw + } else { + # Use fpm for DEB and macOS packages + $Arguments = @() + + $Arguments += Get-FpmArguments ` + -Name $Name ` + -Version $packageVersion ` + -Iteration $Iteration ` + -Description $Description ` + -Type $Type ` + -Dependencies $Dependencies ` + -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` + -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` + -Staging $Staging ` + -Destination $Destination ` + -ManGzipFile $ManGzipInfo.GzipFile ` + -ManDestination $ManGzipInfo.ManFile ` + -LinkInfo $Links ` + -AppsFolder $AppsFolder ` + -Distribution $DebDistro ` + -HostArchitecture $HostArchitecture ` + -ErrorAction Stop + + if ($PSCmdlet.ShouldProcess("Create $type package")) { + Write-Log "Creating package with fpm $Arguments..." + try { + $Output = Start-NativeExecution { fpm $Arguments } + } + catch { + Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue + Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue + Get-Error -InputObject $_ + throw + } } } } finally { @@ -1295,6 +1379,16 @@ function New-UnixPackage { Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo mv $hack_dest $symlink_dest")) -VerboseOutputOnError } } + + # Clean up rpmbuild directory if it was created + if ($Type -eq 'rpm') { + $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" + if (Test-Path $rpmBuildRoot) { + Write-Verbose "Cleaning up rpmbuild directory: $rpmBuildRoot" -Verbose + Remove-Item -Path $rpmBuildRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } + if ($AfterScriptInfo.AfterInstallScript) { Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterInstallScript -Force } @@ -1439,6 +1533,165 @@ Class LinkInfo [string] $Destination } +function New-RpmSpec +{ + param( + [Parameter(Mandatory,HelpMessage='Package Name')] + [String]$Name, + + [Parameter(Mandatory,HelpMessage='Package Version')] + [String]$Version, + + [Parameter(Mandatory)] + [String]$Iteration, + + [Parameter(Mandatory,HelpMessage='Package description')] + [String]$Description, + + [Parameter(Mandatory,HelpMessage='Staging folder for installation files')] + [String]$Staging, + + [Parameter(Mandatory,HelpMessage='Install path on target machine')] + [String]$Destination, + + [Parameter(Mandatory,HelpMessage='The built and gzipped man file.')] + [String]$ManGzipFile, + + [Parameter(Mandatory,HelpMessage='The destination of the man file')] + [String]$ManDestination, + + [Parameter(Mandatory,HelpMessage='Symlink to powershell executable')] + [LinkInfo[]]$LinkInfo, + + [Parameter(Mandatory,HelpMessage='Packages required to install this package')] + [String[]]$Dependencies, + + [Parameter(Mandatory,HelpMessage='Script to run after the package installation.')] + [String]$AfterInstallScript, + + [Parameter(Mandatory,HelpMessage='Script to run after the package removal.')] + [String]$AfterRemoveScript, + + [String]$Distribution = 'rhel.7', + [string]$HostArchitecture + ) + + # RPM doesn't allow hyphens in version, so convert them to underscores + # e.g., "7.6.0-preview.6" becomes Version: 7.6.0_preview.6 + $rpmVersion = $Version -replace '-', '_' + + # Build Release field with distribution suffix (e.g., "1.cm" or "1.rh") + # Don't use RPM macros - build the full release string in PowerShell + $rpmRelease = "$Iteration.$Distribution" + + $specContent = @" +# RPM spec file for PowerShell +# Generated by PowerShell build system + +Name: $Name +Version: $rpmVersion +Release: $rpmRelease +Summary: PowerShell - Cross-platform automation and configuration tool/framework +License: MIT +URL: https://microsoft.com/powershell +AutoReq: no + +"@ + + # Only add BuildArch if not doing cross-architecture build + # For cross-arch builds, we'll rely on --target option + if ($HostArchitecture -eq "x86_64" -or $HostArchitecture -eq "noarch") { + $specContent += "BuildArch: $HostArchitecture`n`n" + } else { + # For cross-architecture builds, don't specify BuildArch in spec + # The --target option will handle the architecture + + # Disable automatic binary stripping for cross-arch builds + # The native /bin/strip on x86_64 cannot process ARM64 binaries and would fail with: + # "Unable to recognise the format of the input file" + # See: https://rpm-software-management.github.io/rpm/manual/macros.html + # __strip: This macro controls the command used for stripping binaries during the build process. + # /bin/true: A command that does nothing and always exits successfully, effectively bypassing the stripping process. + $specContent += "%define __strip /bin/true`n" + + # Disable debug package generation to prevent strip-related errors + # Debug packages require binary stripping which fails for cross-arch builds + # See: https://rpm-packaging-guide.github.io/#debugging + # See: https://docs.fedoraproject.org/en-US/packaging-guidelines/Debuginfo/#_useless_or_incomplete_debuginfo_packages_due_to_other_reasons + $specContent += "%global debug_package %{nil}`n`n" + } + + # Add dependencies + foreach ($dep in $Dependencies) { + $specContent += "Requires: $dep`n" + } + + $specContent += @" + +%description +$Description + +%prep +# No prep needed - files are already staged + +%build +# No build needed - binaries are pre-built + +%install +rm -rf `$RPM_BUILD_ROOT +mkdir -p `$RPM_BUILD_ROOT$Destination +mkdir -p `$RPM_BUILD_ROOT$(Split-Path -Parent $ManDestination) + +# Copy all files from staging to destination +cp -r $Staging/* `$RPM_BUILD_ROOT$Destination/ + +# Copy man page +cp $ManGzipFile `$RPM_BUILD_ROOT$ManDestination + +"@ + + # Add symlinks - we need to get the target of the temp symlink + foreach ($link in $LinkInfo) { + $linkDir = Split-Path -Parent $link.Destination + $specContent += "mkdir -p `$RPM_BUILD_ROOT$linkDir`n" + # For RPM, we copy the symlink itself (which fpm does by including it in the source) + # The symlink at $link.Source points to the actual target, so we'll copy it + # The -P flag preserves symlinks rather than copying their targets, which is critical for this operation. + $specContent += "cp -P $($link.Source) `$RPM_BUILD_ROOT$($link.Destination)`n" + } + + # Post-install script + $postInstallContent = Get-Content -Path $AfterInstallScript -Raw + $specContent += "`n%post`n" + $specContent += $postInstallContent + $specContent += "`n" + + # Post-uninstall script + $postUninstallContent = Get-Content -Path $AfterRemoveScript -Raw + $specContent += "%postun`n" + $specContent += $postUninstallContent + $specContent += "`n" + + # Files section + $specContent += "%files`n" + $specContent += "%defattr(-,root,root,-)`n" + $specContent += "$Destination/*`n" + $specContent += "$ManDestination`n" + + # Add symlinks to files + foreach ($link in $LinkInfo) { + $specContent += "$($link.Destination)`n" + } + + # Changelog with correct date format for RPM + $changelogDate = Get-Date -Format "ddd MMM dd yyyy" + $specContent += "`n%changelog`n" + $specContent += "* $changelogDate PowerShell Team - $rpmVersion-$rpmRelease`n" + $specContent += "- Automated build`n" + + return $specContent +} + function Get-FpmArguments { param( @@ -1651,7 +1904,16 @@ function Get-PackageDependencies function Test-Dependencies { - foreach ($Dependency in "fpm") { + # Note: RPM packages no longer require fpm; they use rpmbuild directly + # DEB packages still use fpm + $Dependencies = @() + + # Only check for fpm on Debian-based systems + if ($Environment.IsDebianFamily) { + $Dependencies += "fpm" + } + + foreach ($Dependency in $Dependencies) { if (!(precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { # These tools are not added to the path automatically on OpenSUSE 13.2 # try adding them to the path and re-tesing first From 810c1eafdf6692b7bf4a4423ef0a41cec5e283da Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:23:27 -0700 Subject: [PATCH 047/255] Update dependabot.yml to monitor release/* branches (#26251) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fdb18f82c9d..45d2e8fe928 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,9 +8,25 @@ updates: labels: - "CL-BuildPackaging" - - package-ecosystem: docker + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "release/*" + schedule: + interval: "daily" + labels: + - "CL-BuildPackaging" + + - package-ecosystem: "docker" directory: / schedule: interval: daily labels: - "CL-BuildPackaging" + + - package-ecosystem: "docker" + directory: "/" + target-branch: "release/*" + schedule: + interval: daily + labels: + - "CL-BuildPackaging" From 90e9159cb5199f4f4e24e759fa255236bd7a3290 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:59:29 -0700 Subject: [PATCH 048/255] Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26243) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .github/actions/build/ci/action.yml | 14 --- .github/actions/test/verify_xunit/action.yml | 21 ---- .../instructions/build-configuration-guide.md | 95 +++++++++++++++++++ .../git-requirements-for-builds.md | 71 ++++++++++++++ .github/instructions/start-psbuild-basics.md | 92 ++++++++++++++++++ .../instructions/troubleshooting-builds.md | 92 ++++++++++++++++++ .../instructions/workflow-prerequisites.md | 91 ++++++++++++++++++ .github/workflows/linux-ci.yml | 19 ++-- .github/workflows/macos-ci.yml | 19 ++-- .github/workflows/windows-ci.yml | 19 ++-- .github/workflows/xunit-tests.yml | 53 +++++++++++ 11 files changed, 515 insertions(+), 71 deletions(-) delete mode 100644 .github/actions/test/verify_xunit/action.yml create mode 100644 .github/instructions/build-configuration-guide.md create mode 100644 .github/instructions/git-requirements-for-builds.md create mode 100644 .github/instructions/start-psbuild-basics.md create mode 100644 .github/instructions/troubleshooting-builds.md create mode 100644 .github/instructions/workflow-prerequisites.md create mode 100644 .github/workflows/xunit-tests.yml diff --git a/.github/actions/build/ci/action.yml b/.github/actions/build/ci/action.yml index 93adaf6b17a..997be8b264c 100644 --- a/.github/actions/build/ci/action.yml +++ b/.github/actions/build/ci/action.yml @@ -31,22 +31,8 @@ runs: Import-Module .\tools\ci.psm1 Invoke-CIBuild shell: pwsh - - name: xUnit Tests - if: success() - continue-on-error: true - run: |- - Write-Verbose -Verbose "Running xUnit tests..." - Import-Module .\tools\ci.psm1 - Restore-PSOptions - Invoke-CIxUnit -SkipFailing - shell: pwsh - name: Upload build artifact uses: actions/upload-artifact@v4 with: name: build path: ${{ runner.workspace }}/build - - name: Upload xunit artifact - uses: actions/upload-artifact@v4 - with: - name: testResults-xunit - path: ${{ runner.workspace }}/xunit diff --git a/.github/actions/test/verify_xunit/action.yml b/.github/actions/test/verify_xunit/action.yml deleted file mode 100644 index fccca27182f..00000000000 --- a/.github/actions/test/verify_xunit/action.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: verify_xunit -description: 'Verify xUnit Results' - -runs: - using: composite - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - path: "${{ github.workspace }}" - - name: Capture artifacts directory - continue-on-error: true - run: dir "${{ github.workspace }}\testResults-xunit\*" -Recurse - shell: pwsh - - name: Test - if: success() - run: |- - Import-Module .\tools\ci.psm1 - $xUnitTestResultsFile = "${{ github.workspace }}\testResults-xunit\xUnitTestResults.xml" - Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile - shell: pwsh diff --git a/.github/instructions/build-configuration-guide.md b/.github/instructions/build-configuration-guide.md new file mode 100644 index 00000000000..d082bcbe774 --- /dev/null +++ b/.github/instructions/build-configuration-guide.md @@ -0,0 +1,95 @@ +# Build Configuration Guide + +## Choosing the Right Configuration + +### For Testing + +**Use: Default (Debug)** + +```yaml +- name: Build for Testing + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +**Why Debug:** +- Includes debugging symbols +- Better error messages +- Faster build times +- Suitable for xUnit and Pester tests + +**Do NOT use:** +- `-Configuration 'Release'` (unnecessary for tests) +- `-ReleaseTag` (not needed for tests) +- `-CI` (unless you specifically need Pester module) + +### For Release/Packaging + +**Use: Release with version tag** + +```yaml +- name: Build for Release + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag +``` + +**Why Release:** +- Optimized binaries +- No debug symbols (smaller size) +- Production-ready + +### For Code Coverage + +**Use: CodeCoverage configuration** + +```yaml +- name: Build with Coverage + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild -Configuration 'CodeCoverage' +``` + +## Platform Considerations + +### All Platforms + +Same commands work across Linux, Windows, and macOS: + +```yaml +strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] +runs-on: ${{ matrix.os }} +steps: + - name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +### Output Locations + +**Linux/macOS:** +``` +src/powershell-unix/bin/Debug///publish/ +``` + +**Windows:** +``` +src/powershell-win-core/bin/Debug///publish/ +``` + +## Best Practices + +1. Use default configuration for testing +2. Avoid redundant parameters +3. Match configuration to purpose +4. Use `-CI` only when needed +5. Always specify `-ReleaseTag` for release or packaging builds diff --git a/.github/instructions/git-requirements-for-builds.md b/.github/instructions/git-requirements-for-builds.md new file mode 100644 index 00000000000..3c8cd91e7c7 --- /dev/null +++ b/.github/instructions/git-requirements-for-builds.md @@ -0,0 +1,71 @@ +# Git Requirements for Building PowerShell + +## Fetch Depth + +**Required:** `fetch-depth: 1000` + +The PowerShell build process uses `git describe --abbrev=60 --long` to generate version information. This requires access to git history and tags. + +### Problem + +Without sufficient fetch depth, builds fail with: +``` +error MSB3073: The command "git describe --abbrev=60 --long" exited with code 128. +``` + +### Solution + +Always use `fetch-depth: 1000` in the checkout step: + +```yaml +- name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 +``` + +## Tag Synchronization + +**Required:** `Sync-PSTags -AddRemoteIfMissing` + +The build process needs git tags to properly version the build. + +### Problem + +Without tag synchronization: +- Version information is incorrect +- Build versioning fails + +### Solution + +Include tag synchronization in the bootstrap step: + +```yaml +- name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Sync-PSTags -AddRemoteIfMissing +``` + +## Complete Example + +```yaml +steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json + + - name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing +``` diff --git a/.github/instructions/start-psbuild-basics.md b/.github/instructions/start-psbuild-basics.md new file mode 100644 index 00000000000..ae216a1584d --- /dev/null +++ b/.github/instructions/start-psbuild-basics.md @@ -0,0 +1,92 @@ +# Start-PSBuild Basics + +## Purpose + +`Start-PSBuild` builds PowerShell from source. It's defined in `build.psm1` and used in CI/CD workflows. + +## Default Usage + +For most scenarios, use with no parameters: + +```powershell +Import-Module ./tools/ci.psm1 +Start-PSBuild +``` + +**Default behavior:** +- Configuration: `Debug` +- PSModuleRestore: Enabled +- Runtime: Auto-detected for platform + +## Common Configurations + +### Debug Build (Default) + +```powershell +Start-PSBuild +``` + +Use for: +- Testing (xUnit, Pester) +- Development +- Debugging + +### Release Build + +```powershell +Start-PSBuild -Configuration 'Release' +``` + +Use for: +- Production packages +- Distribution +- Performance testing + +### Code Coverage Build + +```powershell +Start-PSBuild -Configuration 'CodeCoverage' +``` + +Use for: +- Code coverage analysis +- Test coverage reports + +## Common Parameters + +### -Configuration + +Values: `Debug`, `Release`, `CodeCoverage`, `StaticAnalysis` + +Default: `Debug` + +### -CI + +Restores Pester module for CI environments. + +```powershell +Start-PSBuild -CI +``` + +### -PSModuleRestore + +Now enabled by default. Use `-NoPSModuleRestore` to skip. + +### -ReleaseTag + +Specifies version tag for release builds: + +```powershell +$releaseTag = Get-ReleaseTag +Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag +``` + +## Workflow Example + +```yaml +- name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` diff --git a/.github/instructions/troubleshooting-builds.md b/.github/instructions/troubleshooting-builds.md new file mode 100644 index 00000000000..37f5df00912 --- /dev/null +++ b/.github/instructions/troubleshooting-builds.md @@ -0,0 +1,92 @@ +# Troubleshooting Build Issues + +## Git Describe Error + +**Error:** +``` +error MSB3073: The command "git describe --abbrev=60 --long" exited with code 128. +``` + +**Cause:** Insufficient git history (shallow clone) + +**Solution:** Add `fetch-depth: 1000` to checkout step + +```yaml +- name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 +``` + +## Version Information Incorrect + +**Symptom:** Build produces wrong version numbers + +**Cause:** Git tags not synchronized + +**Solution:** Run `Sync-PSTags -AddRemoteIfMissing`: + +```yaml +- name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing +``` + +## PowerShell Binary Not Built + +**Error:** +``` +Exception: CoreCLR pwsh.exe was not built +``` + +**Causes:** +1. Build failed (check logs) +2. Wrong configuration used +3. Build output location incorrect + +**Solutions:** +1. Check build logs for errors +2. Verify correct configuration for use case +3. Use default parameters: `Start-PSBuild` + +## Module Restore Issues + +**Symptom:** Slow build or module restore failures + +**Causes:** +- Network issues +- Module cache problems +- Package source unavailable + +**Solutions:** +1. Retry the build +2. Check network connectivity +3. Use `-NoPSModuleRestore` if modules not needed +4. Clear package cache if persistent + +## .NET SDK Not Found + +**Symptom:** Build can't find .NET SDK + +**Solution:** Ensure .NET setup step runs first: + +```yaml +- name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json +``` + +## Bootstrap Failures + +**Symptom:** Invoke-CIInstall fails + +**Causes:** +- Missing dependencies +- Network issues +- Platform-specific requirements not met + +**Solution:** Check prerequisites for your platform in build system docs diff --git a/.github/instructions/workflow-prerequisites.md b/.github/instructions/workflow-prerequisites.md new file mode 100644 index 00000000000..fe88abb384f --- /dev/null +++ b/.github/instructions/workflow-prerequisites.md @@ -0,0 +1,91 @@ +# Workflow Prerequisites for Building PowerShell + +## Required Steps Before Start-PSBuild + +These steps must run before calling `Start-PSBuild`: + +### 1. Checkout + +```yaml +- name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 # Required for version generation +``` + +### 2. Setup .NET + +```yaml +- name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json +``` + +### 3. Bootstrap + +```yaml +- name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing +``` + +## Complete Prerequisites Example + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json + + - name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing + + - name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +## Why Each Step Matters + +**Checkout with fetch-depth:** +- Build needs git history for versioning +- Without it: `git describe` fails + +**Setup .NET:** +- Provides SDK for building +- Uses version from global.json + +**Bootstrap:** +- Installs dependencies +- Syncs git tags +- Prepares build environment + +## Optional Steps + +### Environment Capture (Debugging) + +```yaml +- name: Capture Environment + run: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | Write-Verbose -Verbose + shell: pwsh +``` diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 0f0cd110303..450b5f4f147 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -147,20 +147,15 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others - verify_xunit: - name: Verify xUnit test results + xunit_tests: + name: xUnit Tests needs: - - ci_build - changes if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v5 - with: - fetch-depth: 1000 - - name: Verify xUnit test results - uses: "./.github/actions/test/verify_xunit" + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: ubuntu-latest + test_results_artifact_name: testResults-xunit analyze: permissions: @@ -224,7 +219,7 @@ jobs: ready_to_merge: name: Linux ready to merge needs: - - verify_xunit + - xunit_tests - linux_test_elevated_ci - linux_test_elevated_others - linux_test_unelevated_ci diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 0007d550f06..7ec1fc6da06 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -143,20 +143,15 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others - verify_xunit: - name: Verify xUnit test results + xunit_tests: + name: xUnit Tests needs: - - ci_build - changes if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v5 - with: - fetch-depth: 1000 - - name: Verify xUnit test results - uses: "./.github/actions/test/verify_xunit" + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: macos-15-large + test_results_artifact_name: testResults-xunit PackageMac-macos_packaging: name: macOS packaging (bootstrap only) needs: @@ -176,7 +171,7 @@ jobs: ready_to_merge: name: macos ready to merge needs: - - verify_xunit + - xunit_tests - PackageMac-macos_packaging - macos_test_elevated_ci - macos_test_elevated_others diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 64d37eb1ae5..37470475079 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -147,20 +147,15 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others - verify_xunit: - name: Verify xUnit test results + xunit_tests: + name: xUnit Tests needs: - - ci_build - changes if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: windows-latest - steps: - - name: checkout - uses: actions/checkout@v5 - with: - fetch-depth: 1000 - - name: Verify xUnit test results - uses: "./.github/actions/test/verify_xunit" + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: windows-latest + test_results_artifact_name: testResults-xunit windows_packaging: name: Windows Packaging needs: @@ -170,7 +165,7 @@ jobs: ready_to_merge: name: windows ready to merge needs: - - verify_xunit + - xunit_tests - windows_test_elevated_ci - windows_test_elevated_others - windows_test_unelevated_ci diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml new file mode 100644 index 00000000000..8bf5fd699d0 --- /dev/null +++ b/.github/workflows/xunit-tests.yml @@ -0,0 +1,53 @@ +name: xUnit Tests (Reusable) + +on: + workflow_call: + inputs: + runner_os: + description: 'Runner OS for xUnit tests' + type: string + required: false + default: ubuntu-latest + test_results_artifact_name: + description: 'Artifact name for xUnit test results directory' + type: string + required: false + default: testResults-xunit + +jobs: + xunit: + name: Run xUnit Tests + runs-on: ${{ inputs.runner_os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json + + - name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing + + - name: Build PowerShell and run xUnit tests + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild + Write-Host "Running full xUnit test suite (no skipping)..." + Invoke-CIxUnit + Write-Host "Completed xUnit test run." + + - name: Upload xUnit results + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ inputs.test_results_artifact_name }} + path: ${{ github.workspace }}/xUnitTestResults.xml From ae12e4cb8c7694c8f3264c9098a0ffb809f6d202 Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:26:48 -0700 Subject: [PATCH 049/255] Update outdated package references (#26148) --- test/xUnit/xUnit.tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 33f0a2dde00..90ae1a9b453 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -29,8 +29,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + From 38054547f52717e3ead13fc2970bc165f8d58be9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:09:31 -0700 Subject: [PATCH 050/255] Remove unused Azure Devops windows CI workflows (#26245) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .vsts-ci/templates/windows-test.yml | 92 ----------------- .vsts-ci/windows-daily.yml | 148 ---------------------------- 2 files changed, 240 deletions(-) delete mode 100644 .vsts-ci/templates/windows-test.yml delete mode 100644 .vsts-ci/windows-daily.yml diff --git a/.vsts-ci/templates/windows-test.yml b/.vsts-ci/templates/windows-test.yml deleted file mode 100644 index 22758e3953c..00000000000 --- a/.vsts-ci/templates/windows-test.yml +++ /dev/null @@ -1,92 +0,0 @@ -parameters: - pool: 'windows-2019' - imageName: 'PSWindows11-ARM64' - parentJobs: [] - purpose: '' - tagSet: 'CI' - -jobs: -- job: win_test_${{ parameters.purpose }}_${{ parameters.tagSet }} - dependsOn: - ${{ parameters.parentJobs }} - pool: - ${{ if startsWith( parameters.pool, 'windows-') }}: - vmImage: ${{ parameters.pool }} - ${{ else }}: - name: ${{ parameters.pool }} - demands: - - ImageOverride -equals ${{ parameters.imageName }} - - displayName: Windows Test - ${{ parameters.purpose }} - ${{ parameters.tagSet }} - - steps: - - powershell: | - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 - $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue -CommandType Application - - if ($null -eq $pwsh) { - $powerShellPath = Join-Path -Path $env:AGENT_TEMPDIRECTORY -ChildPath 'powershell' - Invoke-WebRequest -Uri https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.ps1 -outfile ./install-powershell.ps1 - ./install-powershell.ps1 -Destination $powerShellPath - $vstsCommandString = "vso[task.setvariable variable=PATH]$powerShellPath;$env:PATH" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - } - - displayName: Install PowerShell if missing - condition: ne('${{ parameters.pool }}', 'windows-2019') - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)" - - # must be run frow Windows PowerShell - - powershell: | - # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. - Write-Host "Old Path:" - Write-Host $env:Path - - $dotnetPath = Join-Path $env:SystemDrive 'Program Files\dotnet' - $paths = $env:Path -split ";" | Where-Object { -not $_.StartsWith($dotnetPath) } - $env:Path = $paths -join ";" - - Write-Host "New Path:" - Write-Host $env:Path - - # Bootstrap - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - - - pwsh: | - Import-Module .\build.psm1 -force - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $path = split-path -path $options.Output - $rootPath = split-Path -path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - Invoke-CITest -Purpose '${{ parameters.purpose }}' -TagSet '${{ parameters.tagSet }}' - displayName: Test - condition: succeeded() diff --git a/.vsts-ci/windows-daily.yml b/.vsts-ci/windows-daily.yml deleted file mode 100644 index 1e9306e9dd1..00000000000 --- a/.vsts-ci/windows-daily.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) -trigger: - # Batch merge builds together while a merge build is running - batch: true - branches: - include: - - master - - release* - - feature* - paths: - include: - - '*' - exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml -pr: - branches: - include: - - master - - release* - - feature* - paths: - include: - - '*' - exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml - -variables: - GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - DOTNET_NOLOGO: 1 - __SuppressAnsiEscapeSequences: 1 - -resources: -- repo: self - clean: true - -stages: -- stage: BuildWin - displayName: Build for Windows - jobs: - - template: templates/ci-build.yml - -- stage: TestWin - displayName: Test for Windows - jobs: - - job: win_test - pool: - vmImage: windows-2019 - displayName: Windows Test - timeoutInMinutes: 90 - - steps: - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: 'Capture Environment' - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - xunit/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - # must be run frow Windows PowerShell - - powershell: | - # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. - Write-Host "Old Path:" - Write-Host $env:Path - - $dotnetPath = Join-Path $env:SystemDrive 'Program Files\dotnet' - $paths = $env:Path -split ";" | Where-Object { -not $_.StartsWith($dotnetPath) } - $env:Path = $paths -join ";" - - Write-Host "New Path:" - Write-Host $env:Path - - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - condition: succeededOrFailed() - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)" - - - pwsh: | - Import-Module .\build.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $path = Split-Path -Parent (Get-PSOutput -Options (Get-PSOptions)) - $rootPath = Split-Path -Path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - displayName: 'Unzip Build' - condition: succeeded() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose UnelevatedPesterTests -TagSet CI - displayName: Test - UnelevatedPesterTests - CI - condition: succeeded() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose ElevatedPesterTests -TagSet CI - displayName: Test - ElevatedPesterTests - CI - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose UnelevatedPesterTests -TagSet Others - displayName: Test - UnelevatedPesterTests - Others - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose ElevatedPesterTests -TagSet Others - displayName: Test - ElevatedPesterTests - Others - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - $xUnitTestResultsFile = '$(System.ArtifactsDirectory)\xunit\xUnitTestResults.xml' - Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile - displayName: Verify xUnit Test Results - condition: succeededOrFailed() From 20cb6d2e8208c1482e8d82503613e9393e014887 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 20 Oct 2025 17:18:06 -0700 Subject: [PATCH 051/255] Update release tags to version 7.5.4 and 7.4.13 (#26258) --- tools/metadata.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/metadata.json b/tools/metadata.json index f9339e276f2..bbe64d0cefe 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,9 +1,9 @@ { - "StableReleaseTag": "v7.5.3", + "StableReleaseTag": "v7.5.4", "PreviewReleaseTag": "v7.6.0-preview.5", "ServicingReleaseTag": "v7.0.13", - "ReleaseTag": "v7.5.3", - "LTSReleaseTag" : ["v7.4.12"], + "ReleaseTag": "v7.5.4", + "LTSReleaseTag" : ["v7.4.13"], "NextReleaseTag": "v7.6.0-preview.6", "LTSRelease": { "Latest": false, "Package": false }, "StableRelease": { "Latest": false, "Package": false } From 92aa0eb90a1d18bf022578bcbb77dbb3d6296535 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:25:59 -0700 Subject: [PATCH 052/255] Add documentation for publishing Pester test results in GitHub Actions (#26254) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../publishing-pester-result.instructions.md | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 .github/instructions/publishing-pester-result.instructions.md diff --git a/.github/instructions/publishing-pester-result.instructions.md b/.github/instructions/publishing-pester-result.instructions.md new file mode 100644 index 00000000000..49010e65a99 --- /dev/null +++ b/.github/instructions/publishing-pester-result.instructions.md @@ -0,0 +1,272 @@ +--- +applyTo: ".github/**/*.{yml,yaml}" +--- + +# Publishing Pester Test Results Instructions + +This document describes how the PowerShell repository uses GitHub Actions to publish Pester test results. + +## Overview + +The PowerShell repository uses a custom composite GitHub Action located at `.github/actions/test/process-pester-results` to process and publish Pester test results in CI/CD workflows. +This action aggregates test results from NUnitXml formatted files, creates a summary in the GitHub Actions job summary, and uploads the results as artifacts. + +## How It Works + +### Action Location and Structure + +**Path**: `.github/actions/test/process-pester-results/` + +The action consists of two main files: + +1. **action.yml** - The composite action definition +1. **process-pester-results.ps1** - PowerShell script that processes test results + +### Action Inputs + +The action accepts the following inputs: + +- **name** (required): A descriptive name for the test run (e.g., "UnelevatedPesterTests-CI") + - Used for naming the uploaded artifact and in the summary + - Format: `junit-pester-{name}` + +- **testResultsFolder** (optional): Path to the folder containing test result XML files + - Default: `${{ runner.workspace }}/testResults` + - The script searches for all `*.xml` files in this folder recursively + +### Action Workflow + +The action performs the following steps: + +1. **Process Test Results** + - Runs `process-pester-results.ps1` with the provided name and test results folder + - Parses all NUnitXml formatted test result files (`*.xml`) + - Aggregates test statistics across all files: + - Total test cases + - Errors + - Failures + - Not run tests + - Inconclusive tests + - Ignored tests + - Skipped tests + - Invalid tests + +1. **Generate Summary** + - Creates a markdown summary using the `$GITHUB_STEP_SUMMARY` environment variable + - Uses `Write-Log` and `Write-LogGroupStart`/`Write-LogGroupEnd` functions from `build.psm1` + - Outputs a formatted summary with all test statistics + - Example format: + + ```markdown + # Summary of {Name} + + - Total Tests: X + - Total Errors: X + - Total Failures: X + - Total Not Run: X + - Total Inconclusive: X + - Total Ignored: X + - Total Skipped: X + - Total Invalid: X + ``` + +1. **Upload Artifacts** + - Uses `actions/upload-artifact@v4` to upload test results + - Artifact name: `junit-pester-{name}` + - Always runs (even if previous steps fail) via `if: always()` + - Uploads the entire test results folder + +1. **Exit Status** + - Fails the job (exit 1) if: + - Any test errors occurred (`$testErrorCount -gt 0`) + - Any test failures occurred (`$testFailureCount -gt 0`) + - No test cases were run (`$testCaseCount -eq 0`) + +## Usage in Test Actions + +The `process-pester-results` action is called by two platform-specific composite test actions: + +### Linux/macOS Tests: `.github/actions/test/nix` + +Used in: + +- `.github/workflows/linux-ci.yml` +- `.github/workflows/macos-ci.yml` + +Example usage (lines 99-104 in `nix/action.yml`): + +```yaml +- name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: "${{ runner.workspace }}/testResults" +``` + +### Windows Tests: `.github/actions/test/windows` + +Used in: + +- `.github/workflows/windows-ci.yml` + +Example usage (line 78-83 in `windows/action.yml`): + +```yaml +- name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: ${{ runner.workspace }}\testResults +``` + +## Workflow Integration + +The process-pester-results action is integrated into the CI workflows through a multi-level hierarchy: + +### Level 1: Main CI Workflows + +- `linux-ci.yml` +- `macos-ci.yml` +- `windows-ci.yml` + +### Level 2: Test Jobs + +Each workflow contains multiple test jobs with different purposes and tag sets: + +- `UnelevatedPesterTests` with tagSet `CI` +- `ElevatedPesterTests` with tagSet `CI` +- `UnelevatedPesterTests` with tagSet `Others` +- `ElevatedPesterTests` with tagSet `Others` + +### Level 3: Platform Test Actions + +Test jobs use platform-specific actions: + +- `nix` for Linux and macOS +- `windows` for Windows + +### Level 4: Process Results Action + +Platform actions call `process-pester-results` to publish results + +## Test Execution Flow + +1. **Build Phase**: Source code is built (e.g., in `ci_build` job) +1. **Test Preparation**: + - Build artifacts are downloaded + - PowerShell is bootstrapped + - Test binaries are extracted +1. **Test Execution**: + - `Invoke-CITest` is called with: + - `-Purpose`: Test purpose (e.g., "UnelevatedPesterTests") + - `-TagSet`: Test category (e.g., "CI", "Others") + - `-OutputFormat NUnitXml`: Results format + - Results are written to `${{ runner.workspace }}/testResults` +1. **Results Processing**: + - `process-pester-results` action runs + - Results are aggregated and summarized + - Artifacts are uploaded + - Job fails if any tests failed or errored + +## Key Dependencies + +### PowerShell Modules + +- **build.psm1**: Provides utility functions + - `Write-Log`: Logging function with GitHub Actions support + - `Write-LogGroupStart`: Creates collapsible log groups + - `Write-LogGroupEnd`: Closes collapsible log groups + +### GitHub Actions Features + +- **GITHUB_STEP_SUMMARY**: Environment variable for job summary +- **actions/upload-artifact@v4**: For uploading test results +- **Composite Actions**: For reusable workflow steps + +### Test Result Format + +- **NUnitXml**: XML format for test results +- Expected XML structure with `test-results` root element containing: + - `total`: Total number of tests + - `errors`: Number of errors + - `failures`: Number of failures + - `not-run`: Number of tests not run + - `inconclusive`: Number of inconclusive tests + - `ignored`: Number of ignored tests + - `skipped`: Number of skipped tests + - `invalid`: Number of invalid tests + +## Best Practices + +1. **Naming Convention**: Use descriptive names that include both purpose and tagSet: + - Format: `{purpose}-{tagSet}` + - Example: `UnelevatedPesterTests-CI` + +1. **Test Results Location**: + - Default location: `${{ runner.workspace }}/testResults` + - Use platform-appropriate path separators (Windows: `\`, Unix: `/`) + +1. **Always Upload**: The artifact upload step uses `if: always()` to ensure results are uploaded even when tests fail + +1. **Error Handling**: The action will fail the job if: + - Tests have errors or failures (intentional fail-fast behavior) + - No tests were executed (potential configuration issue) + - `GITHUB_STEP_SUMMARY` is not set (environment issue) + +## Customizing for Your Repository + +To use this pattern in another repository: + +1. **Copy the Action Files**: + - Copy `.github/actions/test/process-pester-results/` directory + - Ensure the PowerShell script has proper permissions + +1. **Adjust Dependencies**: + - Modify or remove the `Import-Module "$PSScriptRoot/../../../../build.psm1"` line + - Implement equivalent `Write-Log` and `Write-LogGroup*` functions if needed + +1. **Customize Summary Format**: + - Modify the here-string in `process-pester-results.ps1` to change summary format + - Add additional metrics or formatting as needed + +1. **Call from Your Workflows**: + + ```yaml + - name: Process Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "my-test-run" + testResultsFolder: "path/to/results" + ``` + +## Related Documentation + +- [GitHub Actions: Creating composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action) +- [GitHub Actions: Job summaries](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary) +- [GitHub Actions: Uploading artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) +- [Pester: PowerShell testing framework](https://pester.dev/) +- [NUnit XML Format](https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html) + +## Troubleshooting + +### No Test Results Found + +- Verify `testResultsFolder` path is correct +- Ensure tests are generating NUnitXml formatted output +- Check that `*.xml` files exist in the specified folder + +### Action Fails with "GITHUB_STEP_SUMMARY is not set" + +- Ensure the action runs within a GitHub Actions environment +- Cannot be run locally without mocking this environment variable + +### All Tests Pass but Job Fails + +- Check if any tests are marked as errors (different from failures) +- Verify that at least some tests executed (`$testCaseCount -eq 0`) + +### Artifact Upload Fails + +- Check artifact name for invalid characters +- Ensure the test results folder exists +- Verify actions/upload-artifact version compatibility From af26292a7a287af9e7c9c32c8a5997c0e6ff10a6 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 21 Oct 2025 15:13:30 +1000 Subject: [PATCH 053/255] Add PSApplicationOutputEncoding variable (#21219) Adds the $PSApplicationOutputEncoding variable that can be used to control the encoding PowerShell uses when reading the raw bytes to a string of an external application. --- .../engine/InitialSessionState.cs | 8 +++ .../engine/NativeCommandProcessor.cs | 19 +++++- .../engine/SpecialVariables.cs | 5 ++ .../resources/RunspaceInit.resx | 3 + .../Basic/NativeCommandEncoding.Tests.ps1 | 62 +++++++++++++++++++ .../engine/Basic/assets/WriteConsoleOut.ps1 | 23 +++++++ 6 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 create mode 100644 test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 84f513d8450..ba508560ee7 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -4527,6 +4527,14 @@ static InitialSessionState() ScopedItemOptions.None, new ArgumentTypeConverterAttribute(typeof(System.Text.Encoding))), + // Variable which controls the encoding for decoding data from a NativeCommand + new SessionStateVariableEntry( + SpecialVariables.PSApplicationOutputEncoding, + null, + RunspaceInit.PSApplicationOutputEncodingDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(Encoding))), + // Preferences // // NTRAID#Windows Out Of Band Releases-931461-2006/03/13 diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 6b27c4542ab..75e3b468814 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -1645,16 +1645,17 @@ private ProcessStartInfo GetProcessStartInfo( startInfo.UseShellExecute = false; startInfo.RedirectStandardInput = redirectInput; + Encoding outputEncoding = GetOutputEncoding(); if (redirectOutput) { startInfo.RedirectStandardOutput = true; - startInfo.StandardOutputEncoding = Console.OutputEncoding; + startInfo.StandardOutputEncoding = outputEncoding; } if (redirectError) { startInfo.RedirectStandardError = true; - startInfo.StandardErrorEncoding = Console.OutputEncoding; + startInfo.StandardErrorEncoding = outputEncoding; } } @@ -1712,6 +1713,20 @@ private ProcessStartInfo GetProcessStartInfo( return startInfo; } +#nullable enable + /// + /// Gets the encoding to use for a process' output/error pipes. + /// + /// The encoding to use for the process output. + private Encoding GetOutputEncoding() + { + Encoding? applicationOutputEncoding = Context.GetVariableValue( + SpecialVariables.PSApplicationOutputEncodingVarPath) as Encoding; + + return applicationOutputEncoding ?? Console.OutputEncoding; + } +#nullable disable + /// /// Determine if we have a special file which will change the way native argument passing /// is done on Windows. We use legacy behavior for cmd.exe, .bat, .cmd files. diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs index 7563f89a919..51419a0d5c2 100644 --- a/src/System.Management.Automation/engine/SpecialVariables.cs +++ b/src/System.Management.Automation/engine/SpecialVariables.cs @@ -41,6 +41,10 @@ internal static class SpecialVariables internal static readonly VariablePath OutputEncodingVarPath = new VariablePath(OutputEncoding); + internal const string PSApplicationOutputEncoding = nameof(PSApplicationOutputEncoding); + + internal static readonly VariablePath PSApplicationOutputEncodingVarPath = new VariablePath(PSApplicationOutputEncoding); + internal const string VerboseHelpErrors = "VerboseHelpErrors"; internal static readonly VariablePath VerboseHelpErrorsVarPath = new VariablePath(VerboseHelpErrors); @@ -388,6 +392,7 @@ internal static class SpecialVariables SpecialVariables.NestedPromptLevel, SpecialVariables.pwd, SpecialVariables.Matches, + SpecialVariables.PSApplicationOutputEncoding, }, StringComparer.OrdinalIgnoreCase ); diff --git a/src/System.Management.Automation/resources/RunspaceInit.resx b/src/System.Management.Automation/resources/RunspaceInit.resx index f12ba3f71c5..985056b69ee 100644 --- a/src/System.Management.Automation/resources/RunspaceInit.resx +++ b/src/System.Management.Automation/resources/RunspaceInit.resx @@ -153,6 +153,9 @@ The text encoding used when piping text to a native executable file + + The text encoding used when reading output text from a native executable file + Configuration controlling how text is rendered. diff --git a/test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 b/test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 new file mode 100644 index 00000000000..5f044d059e0 --- /dev/null +++ b/test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 @@ -0,0 +1,62 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Native command output encoding tests' -Tags 'CI' { + BeforeAll { + $WriteConsoleOutPath = Join-Path $PSScriptRoot assets WriteConsoleOut.ps1 + $defaultEncoding = [Console]::OutputEncoding.WebName + } + + BeforeEach { + Clear-Variable -Name PSApplicationOutputEncoding + } + + AfterEach { + Clear-Variable -Name PSApplicationOutputEncoding + } + + It 'Defaults to [Console]::OutputEncoding if not set' { + $actual = pwsh -File $WriteConsoleOutPath -Value café -Encoding $defaultEncoding + $actual | Should -Be café + } + + It 'Defaults to [Console]::OutputEncoding if set to $null' { + $PSApplicationOutputEncoding = $null + $actual = pwsh -File $WriteConsoleOutPath -Value café -Encoding $defaultEncoding + $actual | Should -Be café + } + + It 'Uses scoped $PSApplicationOutputEncoding value' { + $PSApplicationOutputEncoding = [System.Text.Encoding]::Unicode + $actual = & { + $PSApplicationOutputEncoding = [System.Text.UTF8Encoding]::new() + pwsh -File $WriteConsoleOutPath -Value café -Encoding utf-8 + } + + $actual | Should -Be café + + # Will use UTF-16-LE hence the different values + $actual = pwsh -File $WriteConsoleOutPath -Value café -Encoding Unicode + $actual | Should -Be café + } + + It 'Uses variable in class method' { + class NativeEncodingTestClass { + static [string] RunTest([string]$Script) { + $PSApplicationOutputEncoding = [System.Text.Encoding]::Unicode + return pwsh -File $Script -Value café -Encoding unicode + } + } + + $actual = [NativeEncodingTestClass]::RunTest($WriteConsoleOutPath) + $actual | Should -Be café + } + + It 'Fails to set variable with invalid encoding object' { + $ps = [PowerShell]::Create() + $ps.AddScript('$PSApplicationOutputEncoding = "utf-8"').Invoke() + + $ps.Streams.Error.Count | Should -Be 1 + [string]$ps.Streams.Error[0] | Should -Be 'Cannot convert the "utf-8" value of type "System.String" to type "System.Text.Encoding".' + } +} diff --git a/test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 b/test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 new file mode 100644 index 00000000000..ef82907a17f --- /dev/null +++ b/test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [string] + $Value, + + [Parameter(Mandatory)] + [string] + $Encoding +) + +$enc = [System.Text.Encoding]::GetEncoding($Encoding) +$data = $enc.GetBytes($Value) + +$outStream = [System.Console]::OpenStandardOutput() +try { + $outStream.Write($data, 0, $data.Length) +} finally { + $outStream.Dispose() +} From 770f8676d2440e7f6366c86936fc343f5c5c5968 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:41:35 -0700 Subject: [PATCH 054/255] Update concurrency groups to prevent merge runs and pull request runs from canceling each other (#26257) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .github/workflows/linux-ci.yml | 2 +- .github/workflows/macos-ci.yml | 2 +- .github/workflows/windows-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 450b5f4f147..18defb738e2 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -27,7 +27,7 @@ on: # Path filters for PRs need to go into the changes job concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ contains(github.ref, 'merge')}} env: diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 7ec1fc6da06..b0242a6d212 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -25,7 +25,7 @@ on: # Path filters for PRs need to go into the changes job concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ contains(github.ref, 'merge')}} env: diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 37470475079..e3fbe7f7185 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -25,7 +25,7 @@ on: # Path filters for PRs need to go into the changes job concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ contains(github.ref, 'merge')}} permissions: From dbc09a1aabf1cdd5120c97de1d04069838f6c7d7 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:43:01 -0500 Subject: [PATCH 055/255] Separate Store Automation Service Endpoints, Resolve AppID (#26210) --- .pipelines/templates/package-create-msix.yml | 22 +++++++- .pipelines/templates/release-MSIX-Publish.yml | 52 ++++++++----------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index e464b612234..f45f3718b04 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -218,13 +218,31 @@ jobs: Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" + + # These variables are used in the next tasks to determine which ServiceEndpoint to use + Write-Host "##vso[task.setvariable variable=LTS]$($IsLTS.ToString().ToLower())" + Write-Host "##vso[task.setvariable variable=STABLE]$($IsStable.ToString().ToLower())" + Write-Host "##vso[task.setvariable variable=PREVIEW]$($IsPreview.ToString().ToLower())" name: UpdateConfigs displayName: Update PDPs and SBConfig.json - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 - displayName: 'Create StoreBroker Package' + displayName: 'Create StoreBroker Package (Preview)' + condition: eq('$(PREVIEW)', 'true') + inputs: + serviceEndpoint: 'StoreAppPublish-Preview' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(BundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Stable/LTS)' + condition: or(eq('$(STABLE)', 'true'), eq('$(LTS)', 'true')) inputs: - serviceEndpoint: 'StoreAppPublish-Private' + serviceEndpoint: 'StoreAppPublish-Stable' sbConfigPath: '$(SBConfigPath)' sourceFolder: '$(BundleDir)' contents: '*.msixBundle' diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml index 4d3e0cb41c8..1735cd9f710 100644 --- a/.pipelines/templates/release-MSIX-Publish.yml +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -50,7 +50,7 @@ jobs: $middleURL = $matches[1] } - $endURL = $tagString -replace '^v|\.', '' + $endURL = $tagString -replace '^v','' -replace '\.','' $message = "Changelog: https://github.com/PowerShell/PowerShell/blob/master/CHANGELOG/$middleURL.md#$endURL" Write-Verbose -Verbose "Release Notes for the Store:" Write-Verbose -Verbose "$message" @@ -73,7 +73,6 @@ jobs: Write-Verbose -Verbose "Channel Selection - LTS: $(LTS), Stable: $(STABLE), Preview: $(PREVIEW)" - # Determine the current channel for logging purposes $currentChannel = if ($IsLTS) { 'LTS' } elseif ($IsStable) { 'Stable' } elseif ($IsPreview) { 'Preview' } @@ -81,17 +80,30 @@ jobs: Write-Error "No valid channel detected" exit 1 } - + + # Assign AppID for Store-Publish Task + $appID = $null + if ($IsLTS) { + $appID = '$(AppID-LTS)' + } + elseif ($IsStable) { + $appID = '$(AppID-Stable)' + } + else { + $appID = '$(AppID-Preview)' + } + + Write-Host "##vso[task.setvariable variable=AppID]$appID" Write-Verbose -Verbose "Selected channel: $currentChannel" Write-Verbose -Verbose "Conditional tasks will handle the publishing based on channel variables" displayName: 'Validate Channel Selection' - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 - displayName: 'Publish LTS StoreBroker Package' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['LTS'], 'true')) + displayName: 'Publish StoreBroker Package (Stable/LTS)' + condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq('$(STABLE)', 'true'), eq('$(LTS)', 'true'))) inputs: - serviceEndpoint: 'StoreAppPublish-Private' - appId: '$(AppID-LTS)' + serviceEndpoint: 'StoreAppPublish-Stable' + appId: '$(AppID)' inputMethod: JsonAndZip jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' @@ -100,32 +112,14 @@ jobs: targetPublishMode: 'Immediate' - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 - displayName: 'Publish Stable StoreBroker Package' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['STABLE'], 'true')) + displayName: 'Publish StoreBroker Package (Preview)' + condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq('$(PREVIEW)', 'true')) inputs: - serviceEndpoint: 'StoreAppPublish-Private' - appId: '$(AppID-Stable)' + serviceEndpoint: 'StoreAppPublish-Preview' + appId: '$(AppID)' inputMethod: JsonAndZip jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' numberOfPackagesToKeep: 2 jsonZipUpdateMetadata: true targetPublishMode: 'Immediate' - - - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 - displayName: 'Publish Preview StoreBroker Package' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['PREVIEW'], 'true')) - inputs: - serviceEndpoint: 'StoreAppPublish-Private' - appId: '$(AppID-Preview)' - inputMethod: JsonAndZip - jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' - zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' - numberOfPackagesToKeep: 2 - jsonZipUpdateMetadata: true - targetPublishMode: 'Immediate' - - - pwsh: | - Get-Content -Path "$(System.DefaultWorkingDirectory)/SBLog.txt" -ErrorAction SilentlyContinue - displayName: Upload Store Failure Log - condition: failed() From eeefcd7bd6950e2812131d495adc10b0d977746f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:45:23 -0400 Subject: [PATCH 056/255] Add GitHub Copilot instruction files for PowerShell CI build system (#26253) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk --- ...ild-checkout-prerequisites.instructions.md | 148 ++++++++++++ ...build-configuration-guide.instructions.md} | 8 + .../git-requirements-for-builds.md | 71 ------ .../instruction-file-format.instructions.md | 220 ++++++++++++++++++ ...d => start-psbuild-basics.instructions.md} | 8 + ...=> troubleshooting-builds.instructions.md} | 8 + .../instructions/workflow-prerequisites.md | 91 -------- 7 files changed, 392 insertions(+), 162 deletions(-) create mode 100644 .github/instructions/build-checkout-prerequisites.instructions.md rename .github/instructions/{build-configuration-guide.md => build-configuration-guide.instructions.md} (94%) delete mode 100644 .github/instructions/git-requirements-for-builds.md create mode 100644 .github/instructions/instruction-file-format.instructions.md rename .github/instructions/{start-psbuild-basics.md => start-psbuild-basics.instructions.md} (93%) rename .github/instructions/{troubleshooting-builds.md => troubleshooting-builds.instructions.md} (94%) delete mode 100644 .github/instructions/workflow-prerequisites.md diff --git a/.github/instructions/build-checkout-prerequisites.instructions.md b/.github/instructions/build-checkout-prerequisites.instructions.md new file mode 100644 index 00000000000..717aa6faa36 --- /dev/null +++ b/.github/instructions/build-checkout-prerequisites.instructions.md @@ -0,0 +1,148 @@ +--- +applyTo: + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Build and Checkout Prerequisites for PowerShell CI + +This document describes the checkout and build prerequisites used in PowerShell's CI workflows. It is intended for GitHub Copilot sessions working with the build system. + +## Overview + +The PowerShell repository uses a standardized build process across Linux, Windows, and macOS CI workflows. Understanding the checkout configuration and the `Sync-PSTags` operation is crucial for working with the build system. + +## Checkout Configuration + +### Fetch Depth + +All CI workflows that build or test PowerShell use `fetch-depth: 1000` in the checkout step: + +```yaml +- name: checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1000 +``` + +**Why 1000 commits?** +- The build system needs access to Git history to determine version information +- `Sync-PSTags` requires sufficient history to fetch and work with tags +- 1000 commits provides a reasonable balance between clone speed and having enough history for version calculation +- Shallow clones (fetch-depth: 1) would break versioning logic + +**Exceptions:** +- The `changes` job uses default fetch depth (no explicit `fetch-depth`) since it only needs to detect file changes +- The `analyze` job (CodeQL) uses `fetch-depth: '0'` (full history) for comprehensive security analysis +- Linux packaging uses `fetch-depth: 0` to ensure all tags are available for package version metadata + +### Workflows Using fetch-depth: 1000 + +- **Linux CI** (`.github/workflows/linux-ci.yml`): All build and test jobs +- **Windows CI** (`.github/workflows/windows-ci.yml`): All build and test jobs +- **macOS CI** (`.github/workflows/macos-ci.yml`): All build and test jobs + +## Sync-PSTags Operation + +### What is Sync-PSTags? + +`Sync-PSTags` is a PowerShell function defined in `build.psm1` that ensures Git tags from the upstream PowerShell repository are synchronized to the local clone. + +### Location + +- **Function Definition**: `build.psm1` (line 36-76) +- **Called From**: + - `.github/actions/build/ci/action.yml` (Bootstrap step, line 24) + - `tools/ci.psm1` (Invoke-CIInstall function, line 146) + +### How It Works + +```powershell +Sync-PSTags -AddRemoteIfMissing +``` + +The function: +1. Searches for a Git remote pointing to the official PowerShell repository: + - `https://github.com/PowerShell/PowerShell` + - `git@github.com:PowerShell/PowerShell` + +2. If no upstream remote exists and `-AddRemoteIfMissing` is specified: + - Adds a remote named `upstream` pointing to `https://github.com/PowerShell/PowerShell.git` + +3. Fetches all tags from the upstream remote: + ```bash + git fetch --tags --quiet upstream + ``` + +4. Sets `$script:tagsUpToDate = $true` to indicate tags are synchronized + +### Why Sync-PSTags is Required + +Tags are critical for: +- **Version Calculation**: `Get-PSVersion` uses `git describe --abbrev=0` to find the latest tag +- **Build Numbering**: CI builds use tag-based versioning for artifacts +- **Changelog Generation**: Release notes are generated based on tags +- **Package Metadata**: Package versions are derived from Git tags + +Without synchronized tags: +- Version detection would fail or return incorrect versions +- Builds might have inconsistent version numbers +- The build process would error when trying to determine the version + +### Bootstrap Step in CI Action + +The `.github/actions/build/ci/action.yml` includes this in the Bootstrap step: + +```yaml +- name: Bootstrap + if: success() + run: |- + Write-Verbose -Verbose "Running Bootstrap..." + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" + shell: pwsh +``` + +**Note**: `Sync-PSTags` is called twice: +1. Once by `Invoke-CIInstall` (in `tools/ci.psm1`) +2. Explicitly again in the Bootstrap step + +This redundancy ensures tags are available even if the first call encounters issues. + +## Best Practices for Copilot Sessions + +When working with the PowerShell CI system: + +1. **Always use `fetch-depth: 1000` or greater** when checking out code for build or test operations +2. **Understand that `Sync-PSTags` requires network access** to fetch tags from the upstream repository +3. **Don't modify the fetch-depth without understanding the impact** on version calculation +4. **If adding new CI workflows**, follow the existing pattern: + - Use `fetch-depth: 1000` for build/test jobs + - Call `Sync-PSTags -AddRemoteIfMissing` during bootstrap + - Ensure the upstream remote is properly configured + +5. **For local development**, developers should: + - Have the upstream remote configured + - Run `Sync-PSTags -AddRemoteIfMissing` before building + - Or use `Start-PSBuild` which handles this automatically + +## Related Files + +- `.github/actions/build/ci/action.yml` - Main CI build action +- `.github/workflows/linux-ci.yml` - Linux CI workflow +- `.github/workflows/windows-ci.yml` - Windows CI workflow +- `.github/workflows/macos-ci.yml` - macOS CI workflow +- `build.psm1` - Contains Sync-PSTags function definition +- `tools/ci.psm1` - CI-specific build functions that call Sync-PSTags + +## Summary + +The PowerShell CI system depends on: +1. **Adequate Git history** (fetch-depth: 1000) for version calculation +2. **Synchronized Git tags** via `Sync-PSTags` for accurate versioning +3. **Upstream remote access** to fetch official repository tags + +These prerequisites ensure consistent, accurate build versioning across all CI platforms. diff --git a/.github/instructions/build-configuration-guide.md b/.github/instructions/build-configuration-guide.instructions.md similarity index 94% rename from .github/instructions/build-configuration-guide.md rename to .github/instructions/build-configuration-guide.instructions.md index d082bcbe774..848aacef496 100644 --- a/.github/instructions/build-configuration-guide.md +++ b/.github/instructions/build-configuration-guide.instructions.md @@ -1,3 +1,11 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + # Build Configuration Guide ## Choosing the Right Configuration diff --git a/.github/instructions/git-requirements-for-builds.md b/.github/instructions/git-requirements-for-builds.md deleted file mode 100644 index 3c8cd91e7c7..00000000000 --- a/.github/instructions/git-requirements-for-builds.md +++ /dev/null @@ -1,71 +0,0 @@ -# Git Requirements for Building PowerShell - -## Fetch Depth - -**Required:** `fetch-depth: 1000` - -The PowerShell build process uses `git describe --abbrev=60 --long` to generate version information. This requires access to git history and tags. - -### Problem - -Without sufficient fetch depth, builds fail with: -``` -error MSB3073: The command "git describe --abbrev=60 --long" exited with code 128. -``` - -### Solution - -Always use `fetch-depth: 1000` in the checkout step: - -```yaml -- name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1000 -``` - -## Tag Synchronization - -**Required:** `Sync-PSTags -AddRemoteIfMissing` - -The build process needs git tags to properly version the build. - -### Problem - -Without tag synchronization: -- Version information is incorrect -- Build versioning fails - -### Solution - -Include tag synchronization in the bootstrap step: - -```yaml -- name: Bootstrap - shell: pwsh - run: | - Import-Module ./tools/ci.psm1 - Sync-PSTags -AddRemoteIfMissing -``` - -## Complete Example - -```yaml -steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1000 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - global-json-file: ./global.json - - - name: Bootstrap - shell: pwsh - run: | - Import-Module ./tools/ci.psm1 - Invoke-CIInstall -SkipUser - Sync-PSTags -AddRemoteIfMissing -``` diff --git a/.github/instructions/instruction-file-format.instructions.md b/.github/instructions/instruction-file-format.instructions.md new file mode 100644 index 00000000000..7c4e0bdd13d --- /dev/null +++ b/.github/instructions/instruction-file-format.instructions.md @@ -0,0 +1,220 @@ +--- +applyTo: + - ".github/instructions/**/*.instructions.md" +--- + +# Instruction File Format Guide + +This document describes the format and guidelines for creating custom instruction files for GitHub Copilot in the PowerShell repository. + +## File Naming Convention + +All instruction files must use the `.instructions.md` suffix: +- ✅ Correct: `build-checkout-prerequisites.instructions.md` +- ✅ Correct: `start-psbuild-basics.instructions.md` +- ❌ Incorrect: `build-guide.md` +- ❌ Incorrect: `instructions.md` + +## Required Frontmatter + +Every instruction file must start with YAML frontmatter containing an `applyTo` section: + +```yaml +--- +applyTo: + - "path/to/files/**/*.ext" + - "specific-file.ext" +--- +``` + +### applyTo Patterns + +Specify which files or directories these instructions apply to: + +**For workflow files:** +```yaml +applyTo: + - ".github/**/*.yml" + - ".github/**/*.yaml" +``` + +**For build scripts:** +```yaml +applyTo: + - "build.psm1" + - "tools/ci.psm1" +``` + +**For multiple contexts:** +```yaml +applyTo: + - "build.psm1" + - "tools/**/*.psm1" + - ".github/**/*.yml" +``` + +## Content Structure + +### 1. Clear Title + +Use a descriptive H1 heading after the frontmatter: + +```markdown +# Build Configuration Guide +``` + +### 2. Purpose or Overview + +Start with a brief explanation of what the instructions cover: + +```markdown +## Purpose + +This guide explains how to configure PowerShell builds for different scenarios. +``` + +### 3. Actionable Content + +Provide clear, actionable guidance: + +**✅ Good - Specific and actionable:** +```markdown +## Default Usage + +Use `Start-PSBuild` with no parameters for testing: + +```powershell +Import-Module ./tools/ci.psm1 +Start-PSBuild +``` +``` + +**❌ Bad - Vague and unclear:** +```markdown +## Usage + +You can use Start-PSBuild to build stuff. +``` + +### 4. Code Examples + +Include working code examples with proper syntax highlighting: + +```markdown +```yaml +- name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` +``` + +### 5. Context and Rationale + +Explain why things are done a certain way: + +```markdown +**Why fetch-depth: 1000?** +- The build system needs Git history for version calculation +- Shallow clones would break versioning logic +``` + +## Best Practices + +### Be Concise + +- Focus on essential information +- Remove redundant explanations +- Use bullet points for lists + +### Be Specific + +- Provide exact commands and parameters +- Include file paths and line numbers when relevant +- Show concrete examples, not abstract concepts + +### Avoid Duplication + +- Don't repeat information from other instruction files +- Reference other files when appropriate +- Keep each file focused on one topic + +### Use Proper Formatting + +**Headers:** +- Use H1 (`#`) for the main title +- Use H2 (`##`) for major sections +- Use H3 (`###`) for subsections + +**Code blocks:** +- Always specify the language: ` ```yaml `, ` ```powershell `, ` ```bash ` +- Keep examples short and focused +- Test examples before including them + +**Lists:** +- Use `-` for unordered lists +- Use `1.` for ordered lists +- Keep list items concise + +## Example Structure + +```markdown +--- +applyTo: + - "relevant/files/**/*.ext" +--- + +# Title of Instructions + +Brief description of what these instructions cover. + +## Section 1 + +Content with examples. + +```language +code example +``` + +## Section 2 + +More specific guidance. + +### Subsection + +Detailed information when needed. + +## Best Practices + +- Actionable tip 1 +- Actionable tip 2 +``` + +## Maintaining Instructions + +### When to Create a New File + +Create a new instruction file when: +- Covering a distinct topic not addressed elsewhere +- The content is substantial enough to warrant its own file +- The `applyTo` scope is different from existing files + +### When to Update an Existing File + +Update an existing file when: +- Information is outdated +- New best practices emerge +- Examples need correction + +### When to Merge or Delete + +Merge or delete files when: +- Content is duplicated across multiple files +- A file is too small to be useful standalone +- Information is no longer relevant + +## Reference + +For more details, see: +- [GitHub Copilot Custom Instructions Documentation](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions) diff --git a/.github/instructions/start-psbuild-basics.md b/.github/instructions/start-psbuild-basics.instructions.md similarity index 93% rename from .github/instructions/start-psbuild-basics.md rename to .github/instructions/start-psbuild-basics.instructions.md index ae216a1584d..18a0026eb2d 100644 --- a/.github/instructions/start-psbuild-basics.md +++ b/.github/instructions/start-psbuild-basics.instructions.md @@ -1,3 +1,11 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + # Start-PSBuild Basics ## Purpose diff --git a/.github/instructions/troubleshooting-builds.md b/.github/instructions/troubleshooting-builds.instructions.md similarity index 94% rename from .github/instructions/troubleshooting-builds.md rename to .github/instructions/troubleshooting-builds.instructions.md index 37f5df00912..e9b60cb8c80 100644 --- a/.github/instructions/troubleshooting-builds.md +++ b/.github/instructions/troubleshooting-builds.instructions.md @@ -1,3 +1,11 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + # Troubleshooting Build Issues ## Git Describe Error diff --git a/.github/instructions/workflow-prerequisites.md b/.github/instructions/workflow-prerequisites.md deleted file mode 100644 index fe88abb384f..00000000000 --- a/.github/instructions/workflow-prerequisites.md +++ /dev/null @@ -1,91 +0,0 @@ -# Workflow Prerequisites for Building PowerShell - -## Required Steps Before Start-PSBuild - -These steps must run before calling `Start-PSBuild`: - -### 1. Checkout - -```yaml -- name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1000 # Required for version generation -``` - -### 2. Setup .NET - -```yaml -- name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - global-json-file: ./global.json -``` - -### 3. Bootstrap - -```yaml -- name: Bootstrap - shell: pwsh - run: | - Import-Module ./tools/ci.psm1 - Invoke-CIInstall -SkipUser - Sync-PSTags -AddRemoteIfMissing -``` - -## Complete Prerequisites Example - -```yaml -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1000 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - global-json-file: ./global.json - - - name: Bootstrap - shell: pwsh - run: | - Import-Module ./tools/ci.psm1 - Invoke-CIInstall -SkipUser - Sync-PSTags -AddRemoteIfMissing - - - name: Build PowerShell - shell: pwsh - run: | - Import-Module ./tools/ci.psm1 - Start-PSBuild -``` - -## Why Each Step Matters - -**Checkout with fetch-depth:** -- Build needs git history for versioning -- Without it: `git describe` fails - -**Setup .NET:** -- Provides SDK for building -- Uses version from global.json - -**Bootstrap:** -- Installs dependencies -- Syncs git tags -- Prepares build environment - -## Optional Steps - -### Environment Capture (Debugging) - -```yaml -- name: Capture Environment - run: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | Write-Verbose -Verbose - shell: pwsh -``` From 67a34a888f4a3e1a3b8da4bb137130ba13793e01 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:51:01 +0000 Subject: [PATCH 057/255] Update linux.md documentation to reflect current CI build configuration (#26255) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .spelling | 1 + docs/building/linux.md | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.spelling b/.spelling index dbc54bbc393..cc711f0aa5a 100644 --- a/.spelling +++ b/.spelling @@ -622,6 +622,7 @@ NativeCommandProcessor.cs NativeCultureResolver nativeexecution net5.0 +net10.0 netcoreapp5.0 netip.ps1. netstandard.dll diff --git a/docs/building/linux.md b/docs/building/linux.md index d1ec01bd206..bd7f9aadf69 100644 --- a/docs/building/linux.md +++ b/docs/building/linux.md @@ -5,7 +5,7 @@ We'll start by showing how to set up your environment from scratch. ## Environment -These instructions are written assuming the Ubuntu 16.04 LTS, since that's the distro the team uses. +These instructions are written assuming Ubuntu 24.04 LTS (the CI uses ubuntu-latest). The build module works on a best-effort basis for other distributions. ### Git Setup @@ -41,15 +41,13 @@ In PowerShell: ```powershell Import-Module ./build.psm1 -Start-PSBootstrap +Start-PSBootstrap -Scenario Both ``` The `Start-PSBootstrap` function does the following: -- Adds the LLVM package feed -- Installs our dependencies combined with the dependencies of the .NET CLI toolchain via `apt-get` -- Uninstalls any prior versions of .NET CLI -- Downloads and installs the .NET Core SDK 2.0.0 to `~/.dotnet` +- Installs build dependencies and packaging tools via `apt-get` (or equivalent package manager) +- Downloads and installs the .NET SDK (currently version 10.0.100-rc.1) to `~/.dotnet` If you want to use `dotnet` outside of `Start-PSBuild`, add `~/.dotnet` to your `PATH` environment variable. @@ -69,7 +67,7 @@ Start-PSBuild -UseNuGetOrg Congratulations! If everything went right, PowerShell is now built. The `Start-PSBuild` script will output the location of the executable: -`./src/powershell-unix/bin/Debug/net6.0/linux-x64/publish/pwsh`. +`./src/powershell-unix/bin/Debug/net10.0/linux-x64/publish/pwsh`. You should now be running the PowerShell Core that you just built, if you run the above executable. You can run our cross-platform Pester tests with `Start-PSPester -UseNuGetOrg`, and our xUnit tests with `Start-PSxUnit`. From 0e3b4c071c84ed358d710ee1635913065ed5d58b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:01:07 -0700 Subject: [PATCH 058/255] Bump actions/setup-dotnet from 4 to 5 (#26264) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/xunit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index 8bf5fd699d0..15533a2c397 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 1000 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: global-json-file: ./global.json From bedefa2733d2d527a2bb3ccbe72dece7a363b72a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:02:31 -0700 Subject: [PATCH 059/255] Bump actions/checkout from 4 to 5 (#26263) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/xunit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index 15533a2c397..c80ee40d039 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ inputs.runner_os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1000 From c6cbd41a6abdeac40157276e756098fd064c484f Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 21 Oct 2025 13:39:12 -0700 Subject: [PATCH 060/255] Add network isolation policy parameter to vPack pipeline (#26223) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .pipelines/PowerShell-vPack-Official.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index 3b151127cb1..7ba92f4eda9 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -24,6 +24,14 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: 'Enable debug output' type: boolean default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" name: vPack_$(Build.SourceBranchName)_Prod.${{ parameters.OfficialBuild }}_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) @@ -54,6 +62,8 @@ variables: value: ${{ iif ( parameters.OfficialBuild, 'v2/Microsoft.Official.yml@onebranchTemplates', 'v2/Microsoft.NonOfficial.yml@onebranchTemplates' ) }} - group: DotNetPrivateBuildAccess - group: certificate_logical_to_actual + - name: netiso + value: ${{ parameters.netiso }} # We shouldn't be using PATs anymore # - group: mscodehub-feed-read-general @@ -70,6 +80,11 @@ extends: platform: name: 'windows_undocked' # windows undocked + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + cloudvault: enabled: false From 7bcdda77e1e328658453be4df4bf12f0fe62f804 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 21 Oct 2025 13:47:59 -0700 Subject: [PATCH 061/255] Add markdown link verification for PRs (#26219) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com> --- .../markdownlinks/Parse-MarkdownLink.ps1 | 182 ++++++++++ .../infrastructure/markdownlinks/README.md | 177 ++++++++++ .../markdownlinks/Verify-MarkdownLinks.ps1 | 317 ++++++++++++++++++ .../infrastructure/markdownlinks/action.yml | 139 ++++++++ ...owershell-parameter-naming.instructions.md | 69 ++++ .github/workflows/verify-markdown-links.yml | 32 ++ 6 files changed, 916 insertions(+) create mode 100644 .github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 create mode 100644 .github/actions/infrastructure/markdownlinks/README.md create mode 100644 .github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 create mode 100644 .github/actions/infrastructure/markdownlinks/action.yml create mode 100644 .github/instructions/powershell-parameter-naming.instructions.md create mode 100644 .github/workflows/verify-markdown-links.yml diff --git a/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 b/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 new file mode 100644 index 00000000000..a56d696eb6e --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 @@ -0,0 +1,182 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#requires -version 7 +# Markdig is always available in PowerShell 7 +<# +.SYNOPSIS + Parse CHANGELOG files using Markdig to extract links. + +.DESCRIPTION + This script uses Markdig.Markdown.Parse to parse all markdown files in the CHANGELOG directory + and extract different types of links (inline links, reference links, etc.). + +.PARAMETER ChangelogPath + Path to the CHANGELOG directory. Defaults to ./CHANGELOG + +.PARAMETER LinkType + Filter by link type: All, Inline, Reference, AutoLink. Defaults to All. + +.EXAMPLE + .\Parse-MarkdownLink.ps1 + +.EXAMPLE + .\Parse-MarkdownLink.ps1 -LinkType Reference +#> + +param( + [string]$ChangelogPath = "./CHANGELOG", + [ValidateSet("All", "Inline", "Reference", "AutoLink")] + [string]$LinkType = "All" +) + +Write-Verbose "Using built-in Markdig functionality to parse markdown files" + +function Get-LinksFromMarkdownAst { + param( + [Parameter(Mandatory)] + [object]$Node, + [Parameter(Mandatory)] + [string]$FileName, + [System.Collections.ArrayList]$Links + ) + + if ($null -eq $Links) { + return + } + + # Check if current node is a link + if ($Node -is [Markdig.Syntax.Inlines.LinkInline]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 # Convert to 1-based line numbering + Column = $Node.Column + 1 # Convert to 1-based column numbering + Url = $Node.Url ?? "" + Text = $Node.FirstChild?.ToString() ?? "" + Type = "Inline" + IsImage = $Node.IsImage + } + [void]$Links.Add($linkInfo) + } + elseif ($Node -is [Markdig.Syntax.Inlines.AutolinkInline]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 + Column = $Node.Column + 1 + Url = $Node.Url ?? "" + Text = $Node.Url ?? "" + Type = "AutoLink" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + elseif ($Node -is [Markdig.Syntax.LinkReferenceDefinitionGroup]) { + foreach ($refDef in $Node) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $refDef.Line + 1 + Column = $refDef.Column + 1 + Url = $refDef.Url ?? "" + Text = $refDef.Label ?? "" + Type = "Reference" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + } + elseif ($Node -is [Markdig.Syntax.LinkReferenceDefinition]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 + Column = $Node.Column + 1 + Url = $Node.Url ?? "" + Text = $Node.Label ?? "" + Type = "Reference" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + + # For MarkdownDocument (root), iterate through all blocks + if ($Node -is [Markdig.Syntax.MarkdownDocument]) { + foreach ($block in $Node) { + Get-LinksFromMarkdownAst -Node $block -FileName $FileName -Links $Links + } + } + # For block containers, iterate through children + elseif ($Node -is [Markdig.Syntax.ContainerBlock]) { + foreach ($child in $Node) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + } + } + # For leaf blocks with inlines, process the inline content + elseif ($Node -is [Markdig.Syntax.LeafBlock] -and $Node.Inline) { + Get-LinksFromMarkdownAst -Node $Node.Inline -FileName $FileName -Links $Links + } + # For inline containers, process all child inlines + elseif ($Node -is [Markdig.Syntax.Inlines.ContainerInline]) { + $child = $Node.FirstChild + while ($child) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + $child = $child.NextSibling + } + } + # For other inline elements that might have children + elseif ($Node.PSObject.Properties.Name -contains "FirstChild" -and $Node.FirstChild) { + $child = $Node.FirstChild + while ($child) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + $child = $child.NextSibling + } + } +} + +function Parse-ChangelogFiles { + param( + [string]$Path + ) + + if (-not (Test-Path $Path)) { + Write-Error "CHANGELOG directory not found: $Path" + return + } + + $markdownFiles = Get-ChildItem -Path $Path -Filter "*.md" -File + + if ($markdownFiles.Count -eq 0) { + Write-Warning "No markdown files found in $Path" + return + } + + $allLinks = [System.Collections.ArrayList]::new() + + foreach ($file in $markdownFiles) { + Write-Verbose "Processing file: $($file.Name)" + + try { + $content = Get-Content -Path $file.FullName -Raw -Encoding UTF8 + + # Parse the markdown content using Markdig + $document = [Markdig.Markdown]::Parse($content, [Markdig.MarkdownPipelineBuilder]::new()) + + # Extract links from the AST + Get-LinksFromMarkdownAst -Node $document -FileName $file.FullName -Links $allLinks + + } catch { + Write-Warning "Error processing file $($file.Name): $($_.Exception.Message)" + } + } + + # Filter by link type if specified + if ($LinkType -ne "All") { + $allLinks = $allLinks | Where-Object { $_.Type -eq $LinkType } + } + + return $allLinks +} + +# Main execution +$links = Parse-ChangelogFiles -Path $ChangelogPath + +# Output PowerShell objects +$links diff --git a/.github/actions/infrastructure/markdownlinks/README.md b/.github/actions/infrastructure/markdownlinks/README.md new file mode 100644 index 00000000000..e566ec2bcc3 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/README.md @@ -0,0 +1,177 @@ +# Verify Markdown Links Action + +A GitHub composite action that verifies all links in markdown files using PowerShell and Markdig. + +## Features + +- ✅ Parses markdown files using Markdig (built into PowerShell 7) +- ✅ Extracts all link types: inline links, reference links, and autolinks +- ✅ Verifies HTTP/HTTPS links with configurable timeouts and retries +- ✅ Validates local file references +- ✅ Supports excluding specific URL patterns +- ✅ Provides detailed error reporting with file locations +- ✅ Outputs metrics for CI/CD integration + +## Usage + +### Basic Usage + +```yaml +- name: Verify Markdown Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' +``` + +### Advanced Usage + +```yaml +- name: Verify Markdown Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './docs' + fail-on-error: 'true' + timeout: 30 + max-retries: 2 + exclude-patterns: '*.example.com/*,*://localhost/*' +``` + +### With Outputs + +```yaml +- name: Verify Markdown Links + id: verify-links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' + fail-on-error: 'false' + +- name: Display Results + run: | + echo "Total links: ${{ steps.verify-links.outputs.total-links }}" + echo "Passed: ${{ steps.verify-links.outputs.passed-links }}" + echo "Failed: ${{ steps.verify-links.outputs.failed-links }}" + echo "Skipped: ${{ steps.verify-links.outputs.skipped-links }}" +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `path` | Path to the directory containing markdown files to verify | No | `./CHANGELOG` | +| `exclude-patterns` | Comma-separated list of URL patterns to exclude from verification | No | `''` | +| `fail-on-error` | Whether to fail the action if any links are broken | No | `true` | +| `timeout` | Timeout in seconds for HTTP requests | No | `30` | +| `max-retries` | Maximum number of retries for failed requests | No | `2` | + +## Outputs + +| Output | Description | +|--------|-------------| +| `total-links` | Total number of unique links checked | +| `passed-links` | Number of links that passed verification | +| `failed-links` | Number of links that failed verification | +| `skipped-links` | Number of links that were skipped | + +## Excluded Link Types + +The action automatically skips the following link types: + +- **Anchor links** (`#section-name`) - Would require full markdown parsing +- **Email links** (`mailto:user@example.com`) - Cannot be verified without sending email + +## GitHub Workflow Test + +This section provides a workflow example and instructions for testing the link verification action. + +### Testing the Workflow + +To test that the workflow properly detects broken links: + +1. Make change to this file (e.g., this README.md file already contains one in the [Broken Link Test](#broken-link-test) section) +1. The workflow will run and should fail, reporting the broken link(s) +1. Revert your change to this file +1. Push again to verify the workflow passes + +### Example Workflow Configuration + +```yaml +name: Verify Links + +on: + push: + branches: [ main ] + paths: + - '**/*.md' + pull_request: + branches: [ main ] + paths: + - '**/*.md' + schedule: + # Run weekly to catch external link rot + - cron: '0 0 * * 0' + +jobs: + verify-links: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Verify CHANGELOG Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' + fail-on-error: 'true' + + - name: Verify Documentation Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './docs' + fail-on-error: 'false' + exclude-patterns: '*.internal.example.com/*' +``` + +## How It Works + +1. **Parse Markdown**: Uses `Parse-MarkdownLink.ps1` to extract all links from markdown files using Markdig +2. **Deduplicate**: Groups links by URL to avoid checking the same link multiple times +3. **Verify Links**: + - HTTP/HTTPS links: Makes HEAD/GET requests with configurable timeout and retries + - Local file references: Checks if the file exists relative to the markdown file + - Excluded patterns: Skips links matching the exclude patterns +4. **Report Results**: Displays detailed results with file locations for failed links +5. **Set Outputs**: Provides metrics for downstream steps + +## Error Output Example + +``` +✗ FAILED: https://example.com/broken-link - HTTP 404 + Found in: /path/to/file.md:42:15 + Found in: /path/to/other.md:100:20 + +Link Verification Summary +============================================================ +Total URLs checked: 150 +Passed: 145 +Failed: 2 +Skipped: 3 + +Failed Links: + • https://example.com/broken-link + Error: HTTP 404 + Occurrences: 2 +``` + +## Requirements + +- PowerShell 7+ (includes Markdig) +- Runs on: `ubuntu-latest`, `windows-latest`, `macos-latest` + +## Broken Link Test + +- [Broken Link](https://github.com/PowerShell/PowerShell/wiki/NonExistentPage404) + +## License + +Same as the PowerShell repository. diff --git a/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 b/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 new file mode 100644 index 00000000000..f50ab1590b9 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 @@ -0,0 +1,317 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 7.0 + +<# +.SYNOPSIS + Verify all links in markdown files. + +.DESCRIPTION + This script parses markdown files to extract links and verifies their accessibility. + It supports HTTP/HTTPS links and local file references. + +.PARAMETER Path + Path to the directory containing markdown files. Defaults to current directory. + +.PARAMETER File + Array of specific markdown files to verify. If provided, Path parameter is ignored. + +.PARAMETER TimeoutSec + Timeout in seconds for HTTP requests. Defaults to 30. + +.PARAMETER MaximumRetryCount + Maximum number of retries for failed requests. Defaults to 2. + +.PARAMETER RetryIntervalSec + Interval in seconds between retry attempts. Defaults to 2. + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -Path ./CHANGELOG + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -Path ./docs -FailOnError + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -File @('CHANGELOG/7.5.md', 'README.md') +#> + +param( + [Parameter(ParameterSetName = 'ByPath', Mandatory)] + [string]$Path = "Q:\src\git\powershell\docs\git", + [Parameter(ParameterSetName = 'ByFile', Mandatory)] + [string[]]$File = @(), + [int]$TimeoutSec = 30, + [int]$MaximumRetryCount = 2, + [int]$RetryIntervalSec = 2 +) + +$ErrorActionPreference = 'Stop' + +# Get the script directory +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Determine what to process: specific files or directory +if ($File.Count -gt 0) { + Write-Host "Extracting links from $($File.Count) specified markdown file(s)" -ForegroundColor Cyan + + # Process each file individually + $allLinks = @() + $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1" + + foreach ($filePath in $File) { + if (Test-Path $filePath) { + Write-Verbose "Processing: $filePath" + $fileLinks = & $parseScriptPath -ChangelogPath $filePath + $allLinks += $fileLinks + } + else { + Write-Warning "File not found: $filePath" + } + } +} +else { + Write-Host "Extracting links from markdown files in: $Path" -ForegroundColor Cyan + + # Get all links from markdown files using the Parse-ChangelogLinks script + $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1" + $allLinks = & $parseScriptPath -ChangelogPath $Path +} + +if ($allLinks.Count -eq 0) { + Write-Host "No links found in markdown files." -ForegroundColor Yellow + exit 0 +} + +Write-Host "Found $($allLinks.Count) links to verify" -ForegroundColor Green + +# Group links by URL to avoid duplicate checks +$uniqueLinks = $allLinks | Group-Object -Property Url + +Write-Host "Unique URLs to verify: $($uniqueLinks.Count)" -ForegroundColor Cyan + +$results = @{ + Total = $uniqueLinks.Count + Passed = 0 + Failed = 0 + Skipped = 0 + Errors = [System.Collections.ArrayList]::new() +} + +function Test-HttpLink { + param( + [string]$Url + ) + + try { + # Try HEAD request first (faster, doesn't download content) + $response = Invoke-WebRequest -Uri $Url ` + -Method Head ` + -TimeoutSec $TimeoutSec ` + -MaximumRetryCount $MaximumRetryCount ` + -RetryIntervalSec $RetryIntervalSec ` + -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com/PowerShell/PowerShell)" ` + -SkipHttpErrorCheck + + # If HEAD fails with 404 or 405, retry with GET (some servers don't support HEAD) + if ($response.StatusCode -eq 404 -or $response.StatusCode -eq 405) { + Write-Verbose "HEAD request failed with $($response.StatusCode), retrying with GET for: $Url" + $response = Invoke-WebRequest -Uri $Url ` + -Method Get ` + -TimeoutSec $TimeoutSec ` + -MaximumRetryCount $MaximumRetryCount ` + -RetryIntervalSec $RetryIntervalSec ` + -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com)" ` + -SkipHttpErrorCheck + } + + if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) { + return @{ Success = $true; StatusCode = $response.StatusCode } + } + else { + return @{ Success = $false; StatusCode = $response.StatusCode; Error = "HTTP $($response.StatusCode)" } + } + } + catch { + return @{ Success = $false; StatusCode = 0; Error = $_.Exception.Message } + } +} + +function Test-LocalLink { + param( + [string]$Url, + [string]$BasePath + ) + + # Strip query parameters (e.g., ?sanitize=true) and anchors (e.g., #section) + $cleanUrl = $Url -replace '\?.*$', '' -replace '#.*$', '' + + # Handle relative paths + $targetPath = Join-Path $BasePath $cleanUrl + + if (Test-Path $targetPath) { + return @{ Success = $true } + } + else { + return @{ Success = $false; Error = "File not found: $targetPath" } + } +} + +# Verify each unique link +$progressCount = 0 +foreach ($linkGroup in $uniqueLinks) { + $progressCount++ + $url = $linkGroup.Name + $occurrences = $linkGroup.Group + Write-Verbose -Verbose "[$progressCount/$($uniqueLinks.Count)] Checking: $url" + + # Determine link type and verify + $verifyResult = $null + if ($url -match '^https?://') { + $verifyResult = Test-HttpLink -Url $url + } + elseif ($url -match '^#') { + Write-Verbose -Verbose "Skipping anchor link: $url" + $results.Skipped++ + continue + } + elseif ($url -match '^mailto:') { + Write-Verbose -Verbose "Skipping mailto link: $url" + $results.Skipped++ + continue + } + else { + $basePath = Split-Path -Parent $occurrences[0].Path + $verifyResult = Test-LocalLink -Url $url -BasePath $basePath + } + if ($verifyResult.Success) { + Write-Host "✓ OK: $url" -ForegroundColor Green + $results.Passed++ + } + else { + $errorMsg = if ($verifyResult.StatusCode) { + "HTTP $($verifyResult.StatusCode)" + } + else { + $verifyResult.Error + } + + # Determine if this status code should be ignored or treated as failure + # Ignore: 401 (Unauthorized), 403 (Forbidden), 429 (Too Many Requests - already retried) + # Fail: 404 (Not Found), 410 (Gone), 406 (Not Acceptable) - these indicate broken links + $shouldIgnore = $false + $ignoreReason = "" + + switch ($verifyResult.StatusCode) { + 401 { + $shouldIgnore = $true + $ignoreReason = "authentication required" + } + 403 { + $shouldIgnore = $true + $ignoreReason = "access forbidden" + } + 429 { + $shouldIgnore = $true + $ignoreReason = "rate limited (already retried)" + } + } + + if ($shouldIgnore) { + Write-Host "⊘ IGNORED: $url - $errorMsg ($ignoreReason)" -ForegroundColor Yellow + Write-Verbose -Verbose "Ignored error details for $url - Status: $($verifyResult.StatusCode) - $ignoreReason" + foreach ($occurrence in $occurrences) { + Write-Verbose -Verbose " Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" + } + $results.Skipped++ + } + else { + Write-Host "✗ FAILED: $url - $errorMsg" -ForegroundColor Red + foreach ($occurrence in $occurrences) { + Write-Host " Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" -ForegroundColor DarkGray + } + $results.Failed++ + [void]$results.Errors.Add(@{ + Url = $url + Error = $errorMsg + Occurrences = $occurrences + }) + } + } + } + +# Print summary +Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan +Write-Host "Link Verification Summary" -ForegroundColor Cyan +Write-Host ("=" * 60) -ForegroundColor Cyan +Write-Host "Total URLs checked: $($results.Total)" -ForegroundColor White +Write-Host "Passed: $($results.Passed)" -ForegroundColor Green +Write-Host "Failed: $($results.Failed)" -ForegroundColor $(if ($results.Failed -gt 0) { "Red" } else { "Green" }) +Write-Host "Skipped: $($results.Skipped)" -ForegroundColor Gray + +if ($results.Failed -gt 0) { + Write-Host "`nFailed Links:" -ForegroundColor Red + foreach ($failedLink in $results.Errors) { + Write-Host " • $($failedLink.Url)" -ForegroundColor Red + Write-Host " Error: $($failedLink.Error)" -ForegroundColor DarkGray + Write-Host " Occurrences: $($failedLink.Occurrences.Count)" -ForegroundColor DarkGray + } + + Write-Host "`n❌ Link verification failed!" -ForegroundColor Red + exit 1 +} +else { + Write-Host "`n✅ All links verified successfully!" -ForegroundColor Green +} + +# Write to GitHub Actions step summary if running in a workflow +if ($env:GITHUB_STEP_SUMMARY) { + $summaryContent = @" + +# Markdown Link Verification Results + +## Summary +- **Total URLs checked:** $($results.Total) +- **Passed:** ✅ $($results.Passed) +- **Failed:** $(if ($results.Failed -gt 0) { "❌" } else { "✅" }) $($results.Failed) +- **Skipped:** $($results.Skipped) + +"@ + + if ($results.Failed -gt 0) { + $summaryContent += @" + +## Failed Links + +| URL | Error | Occurrences | +|-----|-------|-------------| + +"@ + foreach ($failedLink in $results.Errors) { + $summaryContent += "| $($failedLink.Url) | $($failedLink.Error) | $($failedLink.Occurrences.Count) |`n" + } + + $summaryContent += @" + +
+Click to see all failed link locations + +"@ + foreach ($failedLink in $results.Errors) { + $summaryContent += "`n### $($failedLink.Url)`n" + $summaryContent += "**Error:** $($failedLink.Error)`n`n" + foreach ($occurrence in $failedLink.Occurrences) { + $summaryContent += "- `$($occurrence.Path):$($occurrence.Line):$($occurrence.Column)`n" + } + } + $summaryContent += "`n
`n" + } + else { + $summaryContent += "`n## ✅ All links verified successfully!`n" + } + + Write-Verbose -Verbose "Writing `n $summaryContent `n to ${env:GITHUB_STEP_SUMMARY}" + $summaryContent | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append + Write-Verbose -Verbose "Summary written to GitHub Actions step summary" +} + diff --git a/.github/actions/infrastructure/markdownlinks/action.yml b/.github/actions/infrastructure/markdownlinks/action.yml new file mode 100644 index 00000000000..1d6d0864784 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/action.yml @@ -0,0 +1,139 @@ +name: 'Verify Markdown Links' +description: 'Verify all links in markdown files using PowerShell and Markdig' +author: 'PowerShell Team' + +inputs: + timeout-sec: + description: 'Timeout in seconds for HTTP requests' + required: false + default: '30' + maximum-retry-count: + description: 'Maximum number of retries for failed requests' + required: false + default: '2' + +outputs: + total-links: + description: 'Total number of unique links checked' + value: ${{ steps.verify.outputs.total }} + passed-links: + description: 'Number of links that passed verification' + value: ${{ steps.verify.outputs.passed }} + failed-links: + description: 'Number of links that failed verification' + value: ${{ steps.verify.outputs.failed }} + skipped-links: + description: 'Number of links that were skipped' + value: ${{ steps.verify.outputs.skipped }} + +runs: + using: 'composite' + steps: + - name: Get changed markdown files + id: changed-files + uses: actions/github-script@v7 + with: + script: | + let changedMarkdownFiles = []; + + if (context.eventName === 'pull_request') { + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + changedMarkdownFiles = files + .filter(file => file.filename.endsWith('.md')) + .map(file => file.filename); + } else if (context.eventName === 'push') { + const { data: comparison } = await github.rest.repos.compareCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + base: context.payload.before, + head: context.payload.after, + }); + + changedMarkdownFiles = comparison.files + .filter(file => file.filename.endsWith('.md')) + .map(file => file.filename); + } else { + core.setFailed(`Unsupported event type: ${context.eventName}. This action only supports 'pull_request' and 'push' events.`); + return; + } + + console.log('Changed markdown files:', changedMarkdownFiles); + core.setOutput('files', JSON.stringify(changedMarkdownFiles)); + core.setOutput('count', changedMarkdownFiles.length); + return changedMarkdownFiles; + + - name: Verify markdown links + id: verify + shell: pwsh + run: | + Write-Host "Starting markdown link verification..." -ForegroundColor Cyan + + # Get changed markdown files from previous step + $changedFilesJson = '${{ steps.changed-files.outputs.files }}' + $changedFiles = $changedFilesJson | ConvertFrom-Json + + if ($changedFiles.Count -eq 0) { + Write-Host "No markdown files changed, skipping verification" -ForegroundColor Yellow + "total=0" >> $env:GITHUB_OUTPUT + "passed=0" >> $env:GITHUB_OUTPUT + "failed=0" >> $env:GITHUB_OUTPUT + "skipped=0" >> $env:GITHUB_OUTPUT + exit 0 + } + + Write-Host "Changed markdown files: $($changedFiles.Count)" -ForegroundColor Cyan + $changedFiles | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray } + + # Build parameters for each file + $params = @{ + File = $changedFiles + TimeoutSec = [int]'${{ inputs.timeout-sec }}' + MaximumRetryCount = [int]'${{ inputs.maximum-retry-count }}' + } + + # Run the verification script + $scriptPath = Join-Path '${{ github.action_path }}' 'Verify-MarkdownLinks.ps1' + + # Capture output and parse results + $output = & $scriptPath @params 2>&1 | Tee-Object -Variable capturedOutput + + # Try to extract metrics from output + $totalLinks = 0 + $passedLinks = 0 + $failedLinks = 0 + $skippedLinks = 0 + + foreach ($line in $capturedOutput) { + if ($line -match 'Total URLs checked: (\d+)') { + $totalLinks = $Matches[1] + } + elseif ($line -match 'Passed: (\d+)') { + $passedLinks = $Matches[1] + } + elseif ($line -match 'Failed: (\d+)') { + $failedLinks = $Matches[1] + } + elseif ($line -match 'Skipped: (\d+)') { + $skippedLinks = $Matches[1] + } + } + + # Set outputs + "total=$totalLinks" >> $env:GITHUB_OUTPUT + "passed=$passedLinks" >> $env:GITHUB_OUTPUT + "failed=$failedLinks" >> $env:GITHUB_OUTPUT + "skipped=$skippedLinks" >> $env:GITHUB_OUTPUT + + Write-Host "Action completed" -ForegroundColor Cyan + + # Exit with the same code as the verification script + exit $LASTEXITCODE + +branding: + icon: 'link' + color: 'blue' diff --git a/.github/instructions/powershell-parameter-naming.instructions.md b/.github/instructions/powershell-parameter-naming.instructions.md new file mode 100644 index 00000000000..155fd1a85c3 --- /dev/null +++ b/.github/instructions/powershell-parameter-naming.instructions.md @@ -0,0 +1,69 @@ +--- +applyTo: '**/*.ps1, **/*.psm1' +description: Naming conventions for PowerShell parameters +--- + +# PowerShell Parameter Naming Conventions + +## Purpose + +This instruction defines the naming conventions for parameters in PowerShell scripts and modules. Consistent parameter naming improves code readability, maintainability, and usability for users of PowerShell cmdlets and functions. + +## Parameter Naming Rules + +### General Conventions +- **Singular Nouns**: Use singular nouns for parameter names even if the parameter is expected to handle multiple values (e.g., `File` instead of `Files`). +- **Use PascalCase**: Parameter names must use PascalCase (e.g., `ParameterName`). +- **Descriptive Names**: Parameter names should be descriptive and convey their purpose clearly (e.g., `FilePath`, `UserName`). +- **Avoid Abbreviations**: Avoid using abbreviations unless they are widely recognized (e.g., `ID` for Identifier). +- **Avoid Reserved Words**: Do not use PowerShell reserved words as parameter names (e.g., `if`, `else`, `function`). + +### Units and Precision +- **Include Units in Parameter Names**: When a parameter represents a value with units, include the unit in the parameter name for clarity: + - `TimeoutSec` instead of `Timeout` + - `RetryIntervalSec` instead of `RetryInterval` + - `MaxSizeBytes` instead of `MaxSize` +- **Use Full Words for Clarity**: Spell out common terms to match PowerShell conventions: + - `MaximumRetryCount` instead of `MaxRetries` + - `MinimumLength` instead of `MinLength` + +### Alignment with Built-in Cmdlets +- **Follow Existing PowerShell Conventions**: When your parameter serves a similar purpose to a built-in cmdlet parameter, use the same or similar naming: + - Match `Invoke-WebRequest` parameters when making HTTP requests: `TimeoutSec`, `MaximumRetryCount`, `RetryIntervalSec` + - Follow common parameter patterns like `Path`, `Force`, `Recurse`, `WhatIf`, `Confirm` +- **Consistency Within Scripts**: If multiple parameters relate to the same concept, use consistent naming patterns (e.g., `TimeoutSec`, `RetryIntervalSec` both use `Sec` suffix). + +## Examples + +### Good Parameter Names +```powershell +param( + [string[]]$File, # Singular, even though it accepts arrays + [int]$TimeoutSec = 30, # Unit included + [int]$MaximumRetryCount = 2, # Full word "Maximum" + [int]$RetryIntervalSec = 2, # Consistent with TimeoutSec + [string]$Path, # Standard PowerShell convention + [switch]$Force # Common PowerShell parameter +) +``` + +### Names to Avoid +```powershell +param( + [string[]]$Files, # Should be singular: File + [int]$Timeout = 30, # Missing unit: TimeoutSec + [int]$MaxRetries = 2, # Should be: MaximumRetryCount + [int]$RetryInterval = 2, # Missing unit: RetryIntervalSec + [string]$FileLoc, # Avoid abbreviations: FilePath + [int]$Max # Ambiguous: MaximumWhat? +) +``` + +## Exceptions +- **Common Terms**: Some common terms may be used in plural form if they are widely accepted in the context (e.g., `Credentials`, `Permissions`). +- **Legacy Code**: Existing code that does not follow these conventions may be exempted to avoid breaking changes, but new code should adhere to these guidelines. +- **Well Established Naming Patterns**: If a naming pattern is well established in the PowerShell community, it may be used even if it does not strictly adhere to these guidelines. + +## References +- [PowerShell Cmdlet Design Guidelines](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines) +- [About Parameters - PowerShell Documentation](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_parameters) diff --git a/.github/workflows/verify-markdown-links.yml b/.github/workflows/verify-markdown-links.yml new file mode 100644 index 00000000000..db9fb7e416a --- /dev/null +++ b/.github/workflows/verify-markdown-links.yml @@ -0,0 +1,32 @@ +name: Verify Markdown Links + +on: + push: + branches: [ main, master ] + paths: + - '**/*.md' + - '.github/workflows/verify-markdown-links.yml' + - '.github/actions/infrastructure/markdownlinks/**' + pull_request: + branches: [ main, master ] + paths: + - '**/*.md' + schedule: + # Run weekly on Sundays at midnight UTC to catch external link rot + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + verify-markdown-links: + name: Verify Markdown Links + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Verify markdown links + id: verify + uses: ./.github/actions/infrastructure/markdownlinks + with: + timeout-sec: 30 + maximum-retry-count: 2 From c7ee0d2941884ce4437c7913ef64c56b37654454 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Wed, 22 Oct 2025 11:55:23 -0400 Subject: [PATCH 062/255] Fix checks for local user config file paths (#26269) --- .../host/msh/ConsoleHost.cs | 13 +- .../host/msh/UpdatesNotification.cs | 4 +- .../CoreCLR/CorePsPlatform.cs | 46 +- .../engine/CommandDiscovery.cs | 10 +- .../engine/Modules/AnalysisCache.cs | 7 +- .../engine/PSConfiguration.cs | 10 +- .../engine/hostifaces/HostUtilities.cs | 9 +- .../engine/hostifaces/MshHostUserInterface.cs | 5 + .../utils/Telemetry.cs | 927 +++++++++--------- 9 files changed, 552 insertions(+), 479 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index b866815a25c..7228cb31ee2 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -148,13 +148,16 @@ internal static int Start( try { string profileDir = Platform.CacheDirectory; -#if !UNIX - if (!Directory.Exists(profileDir)) + if (!string.IsNullOrEmpty(profileDir)) { - Directory.CreateDirectory(profileDir); - } +#if !UNIX + if (!Directory.Exists(profileDir)) + { + Directory.CreateDirectory(profileDir); + } #endif - ProfileOptimization.SetProfileRoot(profileDir); + ProfileOptimization.SetProfileRoot(profileDir); + } } catch { diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs index 28cd31473dd..b7c001db710 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -57,12 +57,12 @@ internal static class UpdatesNotification static UpdatesNotification() { s_notificationType = GetNotificationType(); - CanNotifyUpdates = s_notificationType != NotificationType.Off; + CanNotifyUpdates = s_notificationType != NotificationType.Off + && Platform.TryDeriveFromCache(PSVersionInfo.GitCommitId, out s_cacheDirectory); if (CanNotifyUpdates) { s_enumOptions = new EnumerationOptions(); - s_cacheDirectory = Path.Combine(Platform.CacheDirectory, PSVersionInfo.GitCommitId); // Build the template/pattern strings for the configured notification type. string typeNum = ((int)s_notificationType).ToString(); diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index f4a7622b66f..8be65502543 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -167,8 +167,13 @@ public static bool IsStaSupported internal static readonly string ConfigDirectory = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG); #else // Gets the location for cache and config folders. - internal static readonly string CacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell"; - internal static readonly string ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"\PowerShell"; + internal static readonly string CacheDirectory = SafeDeriveFromSpecialFolder( + Environment.SpecialFolder.LocalApplicationData, + @"Microsoft\PowerShell"); + + internal static readonly string ConfigDirectory = SafeDeriveFromSpecialFolder( + Environment.SpecialFolder.Personal, + @"PowerShell"); private static readonly Lazy _isStaSupported = new Lazy(() => { @@ -189,6 +194,30 @@ public static bool IsStaSupported private static bool? _isWindowsDesktop = null; #endif + internal static bool TryDeriveFromCache(string path1, out string result) + { + if (CacheDirectory is null or []) + { + result = null; + return false; + } + + result = Path.Combine(CacheDirectory, path1); + return true; + } + + internal static bool TryDeriveFromCache(string path1, string path2, out string result) + { + if (CacheDirectory is null or []) + { + result = null; + return false; + } + + result = Path.Combine(CacheDirectory, path1, path2); + return true; + } + // format files internal static readonly string[] FormatFileNames = new string[] { @@ -218,6 +247,17 @@ internal static class CommonEnvVariableNames #endif } + private static string SafeDeriveFromSpecialFolder(Environment.SpecialFolder specialFolder, string subPath) + { + string basePath = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.DoNotVerify); + if (string.IsNullOrWhiteSpace(basePath)) + { + return string.Empty; + } + + return Path.Join(basePath, subPath); + } + #if UNIX private static string s_tempHome = null; @@ -360,7 +400,7 @@ internal static string GetFolderPath(Environment.SpecialFolder folder) _ => throw new NotSupportedException() }; #else - return Environment.GetFolderPath(folder); + return Environment.GetFolderPath(folder, Environment.SpecialFolderOption.DoNotVerify); #endif } diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index cf4f561c983..9ca87f4c834 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -1231,11 +1231,17 @@ internal LookupPathCollection GetLookupDirectoryPaths() string tempDir = directory.TrimStart(); if (tempDir.EqualsOrdinalIgnoreCase("~")) { - tempDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify); } else if (tempDir.StartsWith("~" + Path.DirectorySeparatorChar)) { - tempDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar + tempDir.Substring(2); + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify) + + Path.DirectorySeparatorChar + + tempDir.Substring(2); } _cachedPath.Add(tempDir); diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 79085041abd..8a5f29e2b16 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -664,6 +664,11 @@ private static byte[] GetHeader() public void QueueSerialization() { + if (string.IsNullOrEmpty(s_cacheStoreLocation)) + { + return; + } + // We expect many modules to rapidly call for serialization. // Instead of doing it right away, we'll queue a task that starts writing // after it seems like we've stopped adding stuff to write out. This is @@ -1121,7 +1126,7 @@ static AnalysisCacheData() cacheFileName = string.Create(CultureInfo.InvariantCulture, $"{cacheFileName}-{hashString}"); } - s_cacheStoreLocation = Path.Combine(Platform.CacheDirectory, cacheFileName); + Platform.TryDeriveFromCache(cacheFileName, out s_cacheStoreLocation); } } diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index e321423f768..419a4cae95f 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -89,7 +89,10 @@ private PowerShellConfig() // Note: This directory may or may not exist depending upon the execution scenario. // Writes will attempt to create the directory if it does not already exist. perUserConfigDirectory = Platform.ConfigDirectory; - perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + if (!string.IsNullOrEmpty(perUserConfigDirectory)) + { + perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + } emptyConfig = new JObject(); configRoots = new JObject[2]; @@ -387,6 +390,11 @@ internal PSKeyword GetLogKeywords() private T ReadValueFromFile(ConfigScope scope, string key, T defaultValue = default) { string fileName = GetConfigFilePath(scope); + if (string.IsNullOrEmpty(fileName)) + { + return defaultValue; + } + JObject configData = configRoots[(int)scope]; if (configData == null) diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index ba71fef0443..7c8ccd0d307 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -208,10 +208,11 @@ internal static string GetFullProfileFileName(string shellId, bool forCurrentUse else { basePath = GetAllUsersFolderPath(shellId); - if (string.IsNullOrEmpty(basePath)) - { - return string.Empty; - } + } + + if (string.IsNullOrEmpty(basePath)) + { + return string.Empty; } string profileName = useTestProfile ? "profile_test.ps1" : "profile.ps1"; diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index 29fc5fa1f7f..5f51ba15751 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -1156,6 +1156,11 @@ internal static string GetTranscriptPath(string baseDirectory, bool includeDate) } } + if (string.IsNullOrEmpty(baseDirectory)) + { + return string.Empty; + } + if (includeDate) { baseDirectory = Path.Combine(baseDirectory, DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture)); diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index 5cd728e6c55..f72ac4eade4 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -163,6 +163,8 @@ public static class ApplicationInsightsTelemetry private static readonly HashSet s_knownSubsystemNames; + private static readonly string s_uuidPath; + /// Gets a value indicating whether telemetry can be sent. public static bool CanSendTelemetry { get; private set; } = false; @@ -176,470 +178,474 @@ public static class ApplicationInsightsTelemetry static ApplicationInsightsTelemetry() { // If we can't send telemetry, there's no reason to do any of this - CanSendTelemetry = !Utils.GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false); - if (CanSendTelemetry) + CanSendTelemetry = !Utils.GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false) + && Platform.TryDeriveFromCache("telemetry.uuid", out s_uuidPath); + + if (!CanSendTelemetry) { - s_sessionId = Guid.NewGuid().ToString(); - TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault(); - configuration.ConnectionString = "InstrumentationKey=" + _psCoreTelemetryKey; + return; + } - // Set this to true to reduce latency during development - configuration.TelemetryChannel.DeveloperMode = false; + s_sessionId = Guid.NewGuid().ToString(); + TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault(); + configuration.ConnectionString = "InstrumentationKey=" + _psCoreTelemetryKey; - // Be sure to obscure any information about the client node name. - configuration.TelemetryInitializers.Add(new NameObscurerTelemetryInitializer()); + // Set this to true to reduce latency during development + configuration.TelemetryChannel.DeveloperMode = false; - s_telemetryClient = new TelemetryClient(configuration); + // Be sure to obscure any information about the client node name. + configuration.TelemetryInitializers.Add(new NameObscurerTelemetryInitializer()); - // use a hashset when looking for module names, it should be quicker than a string comparison - s_knownModules = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "AADRM", - "activedirectory", - "adcsadministration", - "adcsdeployment", - "addsadministration", - "addsdeployment", - "adfs", - "adrms", - "adrmsadmin", - "agpm", - "AIShell", - "appbackgroundtask", - "applocker", - "appv", - "appvclient", - "appvsequencer", - "appvserver", - "appx", - "assignedaccess", - "Az", - "Az.Accounts", - "Az.Advisor", - "Az.Aks", - "Az.AlertsManagement", - "Az.AnalysisServices", - "Az.ApiManagement", - "Az.ApplicationInsights", - "Az.Attestation", - "Az.Automation", - "Az.Batch", - "Az.Billing", - "Az.Blueprint", - "Az.Cdn", - "Az.CognitiveServices", - "Az.Compute", - "Az.ContainerInstance", - "Az.ContainerRegistry", - "Az.DataBox", - "Az.DataFactory", - "Az.DataLakeAnalytics", - "Az.DataLakeStore", - "Az.DataMigration", - "Az.DataShare", - "Az.DeploymentManager", - "Az.DeviceProvisioningServices", - "Az.DevSpaces", - "Az.DevTestLabs", - "Az.Dns", - "Az.EventGrid", - "Az.EventHub", - "Az.FrontDoor", - "Az.GuestConfiguration", - "Az.HDInsight", - "Az.HealthcareApis", - "Az.IotCentral", - "Az.IotHub", - "Az.KeyVault", - "Az.Kusto", - "Az.LogicApp", - "Az.MachineLearning", - "Az.ManagedServiceIdentity", - "Az.ManagedServices", - "Az.ManagementPartner", - "Az.Maps", - "Az.MarketplaceOrdering", - "Az.Media", - "Az.MixedReality", - "Az.Monitor", - "Az.NetAppFiles", - "Az.Network", - "Az.NotificationHubs", - "Az.OperationalInsights", - "Az.Peering", - "Az.PolicyInsights", - "Az.PowerBIEmbedded", - "Az.PrivateDns", - "Az.RecoveryServices", - "Az.RedisCache", - "Az.Relay", - "Az.Reservations", - "Az.ResourceGraph", - "Az.Resources", - "Az.Search", - "Az.Security", - "Az.ServiceBus", - "Az.ServiceFabric", - "Az.SignalR", - "Az.Sql", - "Az.Storage", - "Az.StorageSync", - "Az.StorageTable", - "Az.StreamAnalytics", - "Az.Subscription", - "Az.Tools.Predictor", - "Az.TrafficManager", - "Az.Websites", - "Azs.Azurebridge.Admin", - "Azs.Backup.Admin", - "Azs.Commerce.Admin", - "Azs.Compute.Admin", - "Azs.Fabric.Admin", - "Azs.Gallery.Admin", - "Azs.Infrastructureinsights.Admin", - "Azs.Keyvault.Admin", - "Azs.Network.Admin", - "Azs.Storage.Admin", - "Azs.Subscriptions", - "Azs.Subscriptions.Admin", - "Azs.Update.Admin", - "AzStorageTable", - "Azure", - "Azure.AnalysisServices", - "Azure.Storage", - "AzureAD", - "AzureInformationProtection", - "AzureRM.Aks", - "AzureRM.AnalysisServices", - "AzureRM.ApiManagement", - "AzureRM.ApplicationInsights", - "AzureRM.Automation", - "AzureRM.Backup", - "AzureRM.Batch", - "AzureRM.Billing", - "AzureRM.Cdn", - "AzureRM.CognitiveServices", - "AzureRm.Compute", - "AzureRM.Compute.ManagedService", - "AzureRM.Consumption", - "AzureRM.ContainerInstance", - "AzureRM.ContainerRegistry", - "AzureRM.DataFactories", - "AzureRM.DataFactoryV2", - "AzureRM.DataLakeAnalytics", - "AzureRM.DataLakeStore", - "AzureRM.DataMigration", - "AzureRM.DeploymentManager", - "AzureRM.DeviceProvisioningServices", - "AzureRM.DevSpaces", - "AzureRM.DevTestLabs", - "AzureRm.Dns", - "AzureRM.EventGrid", - "AzureRM.EventHub", - "AzureRM.FrontDoor", - "AzureRM.HDInsight", - "AzureRm.Insights", - "AzureRM.IotCentral", - "AzureRM.IotHub", - "AzureRm.Keyvault", - "AzureRM.LocationBasedServices", - "AzureRM.LogicApp", - "AzureRM.MachineLearning", - "AzureRM.MachineLearningCompute", - "AzureRM.ManagedServiceIdentity", - "AzureRM.ManagementPartner", - "AzureRM.Maps", - "AzureRM.MarketplaceOrdering", - "AzureRM.Media", - "AzureRM.Network", - "AzureRM.NotificationHubs", - "AzureRM.OperationalInsights", - "AzureRM.PolicyInsights", - "AzureRM.PowerBIEmbedded", - "AzureRM.Profile", - "AzureRM.RecoveryServices", - "AzureRM.RecoveryServices.Backup", - "AzureRM.RecoveryServices.SiteRecovery", - "AzureRM.RedisCache", - "AzureRM.Relay", - "AzureRM.Reservations", - "AzureRM.ResourceGraph", - "AzureRM.Resources", - "AzureRM.Scheduler", - "AzureRM.Search", - "AzureRM.Security", - "AzureRM.ServerManagement", - "AzureRM.ServiceBus", - "AzureRM.ServiceFabric", - "AzureRM.SignalR", - "AzureRM.SiteRecovery", - "AzureRM.Sql", - "AzureRm.Storage", - "AzureRM.StorageSync", - "AzureRM.StreamAnalytics", - "AzureRM.Subscription", - "AzureRM.Subscription.Preview", - "AzureRM.Tags", - "AzureRM.TrafficManager", - "AzureRm.UsageAggregates", - "AzureRm.Websites", - "AzureRmStorageTable", - "bestpractices", - "bitlocker", - "bitstransfer", - "booteventcollector", - "branchcache", - "CimCmdlets", - "clusterawareupdating", - "CompatPowerShellGet", - "configci", - "ConfigurationManager", - "CompletionPredictor", - "DataProtectionManager", - "dcbqos", - "deduplication", - "defender", - "devicehealthattestation", - "dfsn", - "dfsr", - "dhcpserver", - "directaccessclient", - "directaccessclientcomponent", - "directaccessclientcomponents", - "dism", - "dnsclient", - "dnsserver", - "ElasticDatabaseJobs", - "EventTracingManagement", - "failoverclusters", - "fileserverresourcemanager", - "FIMAutomation", - "GPRegistryPolicy", - "grouppolicy", - "hardwarecertification", - "hcs", - "hgsattestation", - "hgsclient", - "hgsdiagnostics", - "hgskeyprotection", - "hgsserver", - "hnvdiagnostics", - "hostcomputeservice", - "hpc", - "HPC.ACM", - "HPC.ACM.API.PS", - "HPCPack2016", - "hyper-v", - "IISAdministration", - "international", - "ipamserver", - "iscsi", - "iscsitarget", - "ISE", - "kds", - "Microsoft.MBAM", - "Microsoft.MEDV", - "MgmtSvcAdmin", - "MgmtSvcConfig", - "MgmtSvcMySql", - "MgmtSvcSqlServer", - "Microsoft.AzureStack.ReadinessChecker", - "Microsoft.Crm.PowerShell", - "Microsoft.DiagnosticDataViewer", - "Microsoft.DirectoryServices.MetadirectoryServices.Config", - "Microsoft.Dynamics.Nav.Apps.Management", - "Microsoft.Dynamics.Nav.Apps.Tools", - "Microsoft.Dynamics.Nav.Ide", - "Microsoft.Dynamics.Nav.Management", - "Microsoft.Dynamics.Nav.Model.Tools", - "Microsoft.Dynamics.Nav.Model.Tools.Crm", - "Microsoft.EnterpriseManagement.Warehouse.Cmdlets", - "Microsoft.Medv.Administration.Commands.WorkspacePackager", - "Microsoft.PowerApps.Checker.PowerShell", - "Microsoft.PowerShell.Archive", - "Microsoft.PowerShell.ConsoleGuiTools", - "Microsoft.PowerShell.Core", - "Microsoft.PowerShell.Crescendo", - "Microsoft.PowerShell.Diagnostics", - "Microsoft.PowerShell.Host", - "Microsoft.PowerShell.LocalAccounts", - "Microsoft.PowerShell.Management", - "Microsoft.PowerShell.ODataUtils", - "Microsoft.PowerShell.Operation.Validation", - "Microsoft.PowerShell.PSAdapter", - "Microsoft.PowerShell.PSResourceGet", - "Microsoft.PowerShell.RemotingTools", - "Microsoft.PowerShell.SecretManagement", - "Microsoft.PowerShell.SecretStore", - "Microsoft.PowerShell.Security", - "Microsoft.PowerShell.TextUtility", - "Microsoft.PowerShell.Utility", - "Microsoft.SharePoint.Powershell", - "Microsoft.SystemCenter.ServiceManagementAutomation", - "Microsoft.Windows.ServerManager.Migration", - "Microsoft.WSMan.Management", - "Microsoft.Xrm.OnlineManagementAPI", - "Microsoft.Xrm.Tooling.CrmConnector.PowerShell", - "Microsoft.Xrm.Tooling.PackageDeployment", - "Microsoft.Xrm.Tooling.PackageDeployment.Powershell", - "Microsoft.Xrm.Tooling.Testing", - "MicrosoftPowerBIMgmt", - "MicrosoftPowerBIMgmt.Data", - "MicrosoftPowerBIMgmt.Profile", - "MicrosoftPowerBIMgmt.Reports", - "MicrosoftPowerBIMgmt.Workspaces", - "MicrosoftStaffHub", - "MicrosoftTeams", - "MIMPAM", - "mlSqlPs", - "MMAgent", - "MPIO", - "MsDtc", - "MSMQ", - "MSOnline", - "MSOnlineBackup", - "WmsCmdlets", - "WmsCmdlets3", - "NanoServerImageGenerator", - "NAVWebClientManagement", - "NetAdapter", - "NetConnection", - "NetEventPacketCapture", - "Netlbfo", - "Netldpagent", - "NetNat", - "Netqos", - "NetSecurity", - "NetSwitchtTeam", - "Nettcpip", - "Netwnv", - "NetworkConnectivity", - "NetworkConnectivityStatus", - "NetworkController", - "NetworkControllerDiagnostics", - "NetworkloadBalancingClusters", - "NetworkSwitchManager", - "NetworkTransition", - "NFS", - "NPS", - "OfficeWebapps", - "OperationsManager", - "PackageManagement", - "PartnerCenter", - "pcsvdevice", - "pef", - "Pester", - "pkiclient", - "platformidentifier", - "pnpdevice", - "PowerShellEditorServices", - "PowerShellGet", - "powershellwebaccess", - "printmanagement", - "ProcessMitigations", - "provisioning", - "PSDesiredStateConfiguration", - "PSDiagnostics", - "PSReadLine", - "PSScheduledJob", - "PSScriptAnalyzer", - "PSWorkflow", - "PSWorkflowUtility", - "RemoteAccess", - "RemoteDesktop", - "RemoteDesktopServices", - "ScheduledTasks", - "Secureboot", - "ServerCore", - "ServerManager", - "ServerManagerTasks", - "ServerMigrationcmdlets", - "ServiceFabric", - "Microsoft.Online.SharePoint.PowerShell", - "shieldedvmdatafile", - "shieldedvmprovisioning", - "shieldedvmtemplate", - "SkypeOnlineConnector", - "SkypeForBusinessHybridHealth", - "smbshare", - "smbwitness", - "smisconfig", - "softwareinventorylogging", - "SPFAdmin", - "Microsoft.SharePoint.MigrationTool.PowerShell", - "sqlps", - "SqlServer", - "StartLayout", - "StartScreen", - "Storage", - "StorageDsc", - "storageqos", - "Storagereplica", - "Storagespaces", - "Syncshare", - "System.Center.Service.Manager", - "TLS", - "TroubleshootingPack", - "TrustedPlatformModule", - "UEV", - "UpdateServices", - "UserAccessLogging", - "vamt", - "VirtualMachineManager", - "vpnclient", - "WasPSExt", - "WDAC", - "WDS", - "WebAdministration", - "WebAdministrationDsc", - "WebApplicationProxy", - "WebSites", - "Whea", - "WhiteboardAdmin", - "WindowsDefender", - "WindowsDefenderDsc", - "WindowsDeveloperLicense", - "WindowsDiagnosticData", - "WindowsErrorReporting", - "WindowServerRackup", - "WindowsSearch", - "WindowsServerBackup", - "WindowsUpdate", - "WinGetCommandNotFound", - "wsscmdlets", - "wsssetup", - "wsus", - "xActiveDirectory", - "xBitLocker", - "xDefender", - "xDhcpServer", - "xDismFeature", - "xDnsServer", - "xHyper-V", - "xHyper-VBackup", - "xPSDesiredStateConfiguration", - "xSmbShare", - "xSqlPs", - "xStorage", - "xWebAdministration", - "xWindowsUpdate", - }; - - // use a hashset when looking for module names, it should be quicker than a string comparison - s_knownModuleTags = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "CrescendoBuilt", - }; + s_telemetryClient = new TelemetryClient(configuration); - s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); - s_knownSubsystemNames = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Completion", - "General Feedback", - "Windows Package Manager - WinGet", - "Az Predictor" - }; - } + // use a hashset when looking for module names, it should be quicker than a string comparison + s_knownModules = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "AADRM", + "activedirectory", + "adcsadministration", + "adcsdeployment", + "addsadministration", + "addsdeployment", + "adfs", + "adrms", + "adrmsadmin", + "agpm", + "AIShell", + "appbackgroundtask", + "applocker", + "appv", + "appvclient", + "appvsequencer", + "appvserver", + "appx", + "assignedaccess", + "Az", + "Az.Accounts", + "Az.Advisor", + "Az.Aks", + "Az.AlertsManagement", + "Az.AnalysisServices", + "Az.ApiManagement", + "Az.ApplicationInsights", + "Az.Attestation", + "Az.Automation", + "Az.Batch", + "Az.Billing", + "Az.Blueprint", + "Az.Cdn", + "Az.CognitiveServices", + "Az.Compute", + "Az.ContainerInstance", + "Az.ContainerRegistry", + "Az.DataBox", + "Az.DataFactory", + "Az.DataLakeAnalytics", + "Az.DataLakeStore", + "Az.DataMigration", + "Az.DataShare", + "Az.DeploymentManager", + "Az.DeviceProvisioningServices", + "Az.DevSpaces", + "Az.DevTestLabs", + "Az.Dns", + "Az.EventGrid", + "Az.EventHub", + "Az.FrontDoor", + "Az.GuestConfiguration", + "Az.HDInsight", + "Az.HealthcareApis", + "Az.IotCentral", + "Az.IotHub", + "Az.KeyVault", + "Az.Kusto", + "Az.LogicApp", + "Az.MachineLearning", + "Az.ManagedServiceIdentity", + "Az.ManagedServices", + "Az.ManagementPartner", + "Az.Maps", + "Az.MarketplaceOrdering", + "Az.Media", + "Az.MixedReality", + "Az.Monitor", + "Az.NetAppFiles", + "Az.Network", + "Az.NotificationHubs", + "Az.OperationalInsights", + "Az.Peering", + "Az.PolicyInsights", + "Az.PowerBIEmbedded", + "Az.PrivateDns", + "Az.RecoveryServices", + "Az.RedisCache", + "Az.Relay", + "Az.Reservations", + "Az.ResourceGraph", + "Az.Resources", + "Az.Search", + "Az.Security", + "Az.ServiceBus", + "Az.ServiceFabric", + "Az.SignalR", + "Az.Sql", + "Az.Storage", + "Az.StorageSync", + "Az.StorageTable", + "Az.StreamAnalytics", + "Az.Subscription", + "Az.Tools.Predictor", + "Az.TrafficManager", + "Az.Websites", + "Azs.Azurebridge.Admin", + "Azs.Backup.Admin", + "Azs.Commerce.Admin", + "Azs.Compute.Admin", + "Azs.Fabric.Admin", + "Azs.Gallery.Admin", + "Azs.Infrastructureinsights.Admin", + "Azs.Keyvault.Admin", + "Azs.Network.Admin", + "Azs.Storage.Admin", + "Azs.Subscriptions", + "Azs.Subscriptions.Admin", + "Azs.Update.Admin", + "AzStorageTable", + "Azure", + "Azure.AnalysisServices", + "Azure.Storage", + "AzureAD", + "AzureInformationProtection", + "AzureRM.Aks", + "AzureRM.AnalysisServices", + "AzureRM.ApiManagement", + "AzureRM.ApplicationInsights", + "AzureRM.Automation", + "AzureRM.Backup", + "AzureRM.Batch", + "AzureRM.Billing", + "AzureRM.Cdn", + "AzureRM.CognitiveServices", + "AzureRm.Compute", + "AzureRM.Compute.ManagedService", + "AzureRM.Consumption", + "AzureRM.ContainerInstance", + "AzureRM.ContainerRegistry", + "AzureRM.DataFactories", + "AzureRM.DataFactoryV2", + "AzureRM.DataLakeAnalytics", + "AzureRM.DataLakeStore", + "AzureRM.DataMigration", + "AzureRM.DeploymentManager", + "AzureRM.DeviceProvisioningServices", + "AzureRM.DevSpaces", + "AzureRM.DevTestLabs", + "AzureRm.Dns", + "AzureRM.EventGrid", + "AzureRM.EventHub", + "AzureRM.FrontDoor", + "AzureRM.HDInsight", + "AzureRm.Insights", + "AzureRM.IotCentral", + "AzureRM.IotHub", + "AzureRm.Keyvault", + "AzureRM.LocationBasedServices", + "AzureRM.LogicApp", + "AzureRM.MachineLearning", + "AzureRM.MachineLearningCompute", + "AzureRM.ManagedServiceIdentity", + "AzureRM.ManagementPartner", + "AzureRM.Maps", + "AzureRM.MarketplaceOrdering", + "AzureRM.Media", + "AzureRM.Network", + "AzureRM.NotificationHubs", + "AzureRM.OperationalInsights", + "AzureRM.PolicyInsights", + "AzureRM.PowerBIEmbedded", + "AzureRM.Profile", + "AzureRM.RecoveryServices", + "AzureRM.RecoveryServices.Backup", + "AzureRM.RecoveryServices.SiteRecovery", + "AzureRM.RedisCache", + "AzureRM.Relay", + "AzureRM.Reservations", + "AzureRM.ResourceGraph", + "AzureRM.Resources", + "AzureRM.Scheduler", + "AzureRM.Search", + "AzureRM.Security", + "AzureRM.ServerManagement", + "AzureRM.ServiceBus", + "AzureRM.ServiceFabric", + "AzureRM.SignalR", + "AzureRM.SiteRecovery", + "AzureRM.Sql", + "AzureRm.Storage", + "AzureRM.StorageSync", + "AzureRM.StreamAnalytics", + "AzureRM.Subscription", + "AzureRM.Subscription.Preview", + "AzureRM.Tags", + "AzureRM.TrafficManager", + "AzureRm.UsageAggregates", + "AzureRm.Websites", + "AzureRmStorageTable", + "bestpractices", + "bitlocker", + "bitstransfer", + "booteventcollector", + "branchcache", + "CimCmdlets", + "clusterawareupdating", + "CompatPowerShellGet", + "configci", + "ConfigurationManager", + "CompletionPredictor", + "DataProtectionManager", + "dcbqos", + "deduplication", + "defender", + "devicehealthattestation", + "dfsn", + "dfsr", + "dhcpserver", + "directaccessclient", + "directaccessclientcomponent", + "directaccessclientcomponents", + "dism", + "dnsclient", + "dnsserver", + "ElasticDatabaseJobs", + "EventTracingManagement", + "failoverclusters", + "fileserverresourcemanager", + "FIMAutomation", + "GPRegistryPolicy", + "grouppolicy", + "hardwarecertification", + "hcs", + "hgsattestation", + "hgsclient", + "hgsdiagnostics", + "hgskeyprotection", + "hgsserver", + "hnvdiagnostics", + "hostcomputeservice", + "hpc", + "HPC.ACM", + "HPC.ACM.API.PS", + "HPCPack2016", + "hyper-v", + "IISAdministration", + "international", + "ipamserver", + "iscsi", + "iscsitarget", + "ISE", + "kds", + "Microsoft.MBAM", + "Microsoft.MEDV", + "MgmtSvcAdmin", + "MgmtSvcConfig", + "MgmtSvcMySql", + "MgmtSvcSqlServer", + "Microsoft.AzureStack.ReadinessChecker", + "Microsoft.Crm.PowerShell", + "Microsoft.DiagnosticDataViewer", + "Microsoft.DirectoryServices.MetadirectoryServices.Config", + "Microsoft.Dynamics.Nav.Apps.Management", + "Microsoft.Dynamics.Nav.Apps.Tools", + "Microsoft.Dynamics.Nav.Ide", + "Microsoft.Dynamics.Nav.Management", + "Microsoft.Dynamics.Nav.Model.Tools", + "Microsoft.Dynamics.Nav.Model.Tools.Crm", + "Microsoft.EnterpriseManagement.Warehouse.Cmdlets", + "Microsoft.Medv.Administration.Commands.WorkspacePackager", + "Microsoft.PowerApps.Checker.PowerShell", + "Microsoft.PowerShell.Archive", + "Microsoft.PowerShell.ConsoleGuiTools", + "Microsoft.PowerShell.Core", + "Microsoft.PowerShell.Crescendo", + "Microsoft.PowerShell.Diagnostics", + "Microsoft.PowerShell.Host", + "Microsoft.PowerShell.LocalAccounts", + "Microsoft.PowerShell.Management", + "Microsoft.PowerShell.ODataUtils", + "Microsoft.PowerShell.Operation.Validation", + "Microsoft.PowerShell.PSAdapter", + "Microsoft.PowerShell.PSResourceGet", + "Microsoft.PowerShell.RemotingTools", + "Microsoft.PowerShell.SecretManagement", + "Microsoft.PowerShell.SecretStore", + "Microsoft.PowerShell.Security", + "Microsoft.PowerShell.TextUtility", + "Microsoft.PowerShell.Utility", + "Microsoft.SharePoint.Powershell", + "Microsoft.SystemCenter.ServiceManagementAutomation", + "Microsoft.Windows.ServerManager.Migration", + "Microsoft.WSMan.Management", + "Microsoft.Xrm.OnlineManagementAPI", + "Microsoft.Xrm.Tooling.CrmConnector.PowerShell", + "Microsoft.Xrm.Tooling.PackageDeployment", + "Microsoft.Xrm.Tooling.PackageDeployment.Powershell", + "Microsoft.Xrm.Tooling.Testing", + "MicrosoftPowerBIMgmt", + "MicrosoftPowerBIMgmt.Data", + "MicrosoftPowerBIMgmt.Profile", + "MicrosoftPowerBIMgmt.Reports", + "MicrosoftPowerBIMgmt.Workspaces", + "MicrosoftStaffHub", + "MicrosoftTeams", + "MIMPAM", + "mlSqlPs", + "MMAgent", + "MPIO", + "MsDtc", + "MSMQ", + "MSOnline", + "MSOnlineBackup", + "WmsCmdlets", + "WmsCmdlets3", + "NanoServerImageGenerator", + "NAVWebClientManagement", + "NetAdapter", + "NetConnection", + "NetEventPacketCapture", + "Netlbfo", + "Netldpagent", + "NetNat", + "Netqos", + "NetSecurity", + "NetSwitchtTeam", + "Nettcpip", + "Netwnv", + "NetworkConnectivity", + "NetworkConnectivityStatus", + "NetworkController", + "NetworkControllerDiagnostics", + "NetworkloadBalancingClusters", + "NetworkSwitchManager", + "NetworkTransition", + "NFS", + "NPS", + "OfficeWebapps", + "OperationsManager", + "PackageManagement", + "PartnerCenter", + "pcsvdevice", + "pef", + "Pester", + "pkiclient", + "platformidentifier", + "pnpdevice", + "PowerShellEditorServices", + "PowerShellGet", + "powershellwebaccess", + "printmanagement", + "ProcessMitigations", + "provisioning", + "PSDesiredStateConfiguration", + "PSDiagnostics", + "PSReadLine", + "PSScheduledJob", + "PSScriptAnalyzer", + "PSWorkflow", + "PSWorkflowUtility", + "RemoteAccess", + "RemoteDesktop", + "RemoteDesktopServices", + "ScheduledTasks", + "Secureboot", + "ServerCore", + "ServerManager", + "ServerManagerTasks", + "ServerMigrationcmdlets", + "ServiceFabric", + "Microsoft.Online.SharePoint.PowerShell", + "shieldedvmdatafile", + "shieldedvmprovisioning", + "shieldedvmtemplate", + "SkypeOnlineConnector", + "SkypeForBusinessHybridHealth", + "smbshare", + "smbwitness", + "smisconfig", + "softwareinventorylogging", + "SPFAdmin", + "Microsoft.SharePoint.MigrationTool.PowerShell", + "sqlps", + "SqlServer", + "StartLayout", + "StartScreen", + "Storage", + "StorageDsc", + "storageqos", + "Storagereplica", + "Storagespaces", + "Syncshare", + "System.Center.Service.Manager", + "TLS", + "TroubleshootingPack", + "TrustedPlatformModule", + "UEV", + "UpdateServices", + "UserAccessLogging", + "vamt", + "VirtualMachineManager", + "vpnclient", + "WasPSExt", + "WDAC", + "WDS", + "WebAdministration", + "WebAdministrationDsc", + "WebApplicationProxy", + "WebSites", + "Whea", + "WhiteboardAdmin", + "WindowsDefender", + "WindowsDefenderDsc", + "WindowsDeveloperLicense", + "WindowsDiagnosticData", + "WindowsErrorReporting", + "WindowServerRackup", + "WindowsSearch", + "WindowsServerBackup", + "WindowsUpdate", + "WinGetCommandNotFound", + "wsscmdlets", + "wsssetup", + "wsus", + "xActiveDirectory", + "xBitLocker", + "xDefender", + "xDhcpServer", + "xDismFeature", + "xDnsServer", + "xHyper-V", + "xHyper-VBackup", + "xPSDesiredStateConfiguration", + "xSmbShare", + "xSqlPs", + "xStorage", + "xWebAdministration", + "xWindowsUpdate", + }; + + // use a hashset when looking for module names, it should be quicker than a string comparison + s_knownModuleTags = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "CrescendoBuilt", + }; + + s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); + s_knownSubsystemNames = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Completion", + "General Feedback", + "Windows Package Manager - WinGet", + "Az Predictor" + }; } /// @@ -984,8 +990,7 @@ private static Guid GetUniqueIdentifier() // Try to get the unique id. If this returns false, we'll // create/recreate the telemetry.uuid file to persist for next startup. Guid id = Guid.Empty; - string uuidPath = Path.Join(Platform.CacheDirectory, "telemetry.uuid"); - if (TryGetIdentifier(uuidPath, out id)) + if (TryGetIdentifier(s_uuidPath, out id)) { return id; } @@ -1000,7 +1005,7 @@ private static Guid GetUniqueIdentifier() m.WaitOne(); try { - return CreateUniqueIdentifierAndFile(uuidPath); + return CreateUniqueIdentifierAndFile(s_uuidPath); } finally { From 1b3a3215d2ef79ac3c937a5b7e422d211adb2f09 Mon Sep 17 00:00:00 2001 From: ThioJoe <12518330+ThioJoe@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:09:26 -0700 Subject: [PATCH 063/255] Fix a few simple typos in comments and string outputs (#25805) Co-authored-by: Travis Plunk --- assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 | 4 ++-- .../engine/remoting/common/RunspaceConnectionInfo.cs | 2 +- .../Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 | 2 +- .../Microsoft.PowerShell.RemotingTools.psm1 | 2 +- tools/packaging/packaging.psm1 | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 b/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 index db756063da4..da89768f74a 100644 --- a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 +++ b/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 @@ -17,14 +17,14 @@ switch ($TestHook) { } default { $jobScript = { - # This registers Microsoft Update via a predifened GUID with the Windows Update Agent. + # This registers Microsoft Update via a predefined GUID with the Windows Update Agent. # https://learn.microsoft.com/windows/win32/wua_sdk/opt-in-to-microsoft-update $serviceManager = (New-Object -ComObject Microsoft.Update.ServiceManager) $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu if (!$isRegistered) { - Write-Verbose -Verbose "Opting into Microsoft Update as the Autmatic Update Service" + Write-Verbose -Verbose "Opting into Microsoft Update as the Automatic Update Service" # 7 is the combination of asfAllowPendingRegistration, asfAllowOnlineRegistration, asfRegisterServiceWithAU # AU means Automatic Updates $null = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '') diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index 5f25851337e..b6913e0cc1c 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -2255,7 +2255,7 @@ internal int StartSSHProcess( DiscoveryExceptions.CommandNotFoundException); } - // Create a local ssh process (client) that conects to a remote sshd process (server) using a 'powershell' subsystem. + // Create a local ssh process (client) that connects to a remote sshd process (server) using a 'powershell' subsystem. // // Local ssh invoked as: // windows: diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 index 27d5cfb8359..58f6a4f3353 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 @@ -286,7 +286,7 @@ Describe "Deserializing corrupted Cim classes should not instantiate non-Cim typ BeforeAll { # Only run on Windows platform. - # Ensure calc.exe is avaiable for test. + # Ensure calc.exe is available for test. $shouldRunTest = $IsWindows -and ((Get-Command calc.exe -ErrorAction SilentlyContinue) -ne $null) $skipNotWindows = ! $shouldRunTest if ( $shouldRunTest ) diff --git a/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 b/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 index 41a57772fd3..911aa8da0ce 100644 --- a/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 +++ b/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 @@ -321,7 +321,7 @@ $typeDef = @' .Parameter PowerShellFilePath Specifies the file path to the PowerShell command used to host the SSH remoting PowerShell endpoint. If no value is specified then the currently running PowerShell executable path is used - in the subsytem command. + in the subsystem command. .Parameter Force When true, this cmdlet will update the sshd_config configuration file without prompting. #> diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 894323a4b98..435fe2d49bc 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1069,7 +1069,7 @@ function New-UnixPackage { DynamicParam { if ($Type -eq "deb" -or $Type -like 'rpm*') { # Add a dynamic parameter '-Distribution' when the specified package type is 'deb'. - # The '-Distribution' parameter can be used to indicate which Debian distro this pacakge is targeting. + # The '-Distribution' parameter can be used to indicate which Debian distro this package is targeting. $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" if($type -eq 'deb') { @@ -1838,7 +1838,7 @@ function Get-PackageDependencies param() DynamicParam { # Add a dynamic parameter '-Distribution' when the specified package type is 'deb'. - # The '-Distribution' parameter can be used to indicate which Debian distro this pacakge is targeting. + # The '-Distribution' parameter can be used to indicate which Debian distro this package is targeting. $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" $ParameterAttr.Mandatory = $true $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList $Script:AllDistributions From e5d40dc06de24cf3fe6d6316414673af8aba5d2e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 23 Oct 2025 11:15:30 -0700 Subject: [PATCH 064/255] Update PSReadLine to v2.4.5 (#26282) --- src/Modules/PSGalleryModules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index 327c293e72c..6cec561a07a 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -15,7 +15,7 @@ - + From b0507ca4cf9f70cc184336a7fcffee980bd40380 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 23 Oct 2025 11:21:01 -0700 Subject: [PATCH 065/255] Fix build to only enable ready-to-run for the Release configuration (#26290) --- PowerShell.Common.props | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PowerShell.Common.props b/PowerShell.Common.props index 91d44c21f03..dfc16f830d7 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -193,12 +193,20 @@ Global + + + + true true AppLocal + + + + true true From 38f9885173eeea363e9650b317d3c35149540f17 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 23 Oct 2025 16:19:55 -0700 Subject: [PATCH 066/255] Fix a regression in the API `CompletionCompleters.CompleteFilename()` that causes null reference exception (#26291) --- .../CommandCompletion/CompletionCompleters.cs | 4 ++-- .../Host/TabCompletion/BugFix.Tests.ps1 | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 80ba4545ff4..335a704dd62 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -4580,8 +4580,8 @@ internal static IEnumerable CompleteFilename(CompletionContext return CommandCompletion.EmptyCompletionResult; } - var lastAst = context.RelatedAsts[^1]; - if (lastAst.Parent is UsingStatementAst usingStatement + var lastAst = context.RelatedAsts?[^1]; + if (lastAst?.Parent is UsingStatementAst usingStatement && usingStatement.UsingStatementKind is UsingStatementKind.Module or UsingStatementKind.Assembly && lastAst.Extent.File is not null) { diff --git a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 index 32fd73f8c09..a7191d06e9b 100644 --- a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 @@ -126,4 +126,27 @@ Describe "Tab completion bug fix" -Tags "CI" { $Runspace.Dispose() } } + + It "Issue#26277 - [CompletionCompleters]::CompleteFilename('') should work" { + $testDir = Join-Path $TestDrive "TempTestDir" + $file1 = Join-Path $testDir "abc.ps1" + $file2 = Join-Path $testDir "def.py" + + New-Item -ItemType Directory -Path $testDir > $null + New-Item -ItemType File -Path $file1 > $null + New-Item -ItemType File -Path $file2 > $null + + try { + Push-Location -Path $testDir + $result = [System.Management.Automation.CompletionCompleters]::CompleteFilename("") + $result | Should -Not -Be $null + $result | Measure-Object | ForEach-Object -MemberName Count | Should -Be 2 + + $item1, $item2 = @($result) + $item1.ListItemText | Should -BeExactly 'abc.ps1' + $item2.ListItemText | Should -BeExactly 'def.py' + } finally { + Pop-Location + } + } } From 7ec01116351c928e79951c91a2b62f839538bf40 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Thu, 23 Oct 2025 20:21:16 -0700 Subject: [PATCH 067/255] Create github copilot setup workflow (#26285) Co-authored-by: Dongbo Wang --- .github/workflows/copilot-setup-steps.yml | 61 +++++++++++++ build.psm1 | 106 ++++++++++++++++++++-- 2 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000000..ac57bd87b5a --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,61 @@ +name: "Copilot Setup Steps" + +# Allow testing of the setup steps from your repository's "Actions" tab. +on: + workflow_dispatch: + + pull_request: + branches: + - master + paths: + - ".github/workflows/copilot-setup-steps.yml" + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + # See https://docs.github.com/en/copilot/customizing-copilot/customizing-the-development-environment-for-copilot-coding-agent + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 1000 + + - name: Bootstrap + if: success() + run: |- + $title = 'Import Build.psm1' + Write-Host "::group::$title" + Import-Module ./build.psm1 -Verbose -ErrorAction Stop + Write-LogGroupEnd -Title $title + + $title = 'Switch to public feed' + Write-LogGroupStart -Title $title + Switch-PSNugetConfig -Source Public + Write-LogGroupEnd -Title $title + + $title = 'Bootstrap' + Write-LogGroupStart -Title $title + Start-PSBootstrap -Scenario DotNet + Write-LogGroupEnd -Title $title + + $title = 'Install .NET Tools' + Write-LogGroupStart -Title $title + Start-PSBootstrap -Scenario Tools + Write-LogGroupEnd -Title $title + + $title = 'Sync Tags' + Write-LogGroupStart -Title $title + Sync-PSTags -AddRemoteIfMissing + Write-LogGroupEnd -Title $title + + $title = 'Setup .NET environment variables' + Write-LogGroupStart -Title $title + Find-DotNet -SetDotnetRoot + Write-LogGroupEnd -Title $title + shell: pwsh diff --git a/build.psm1 b/build.psm1 index 88878bdd635..1b56d9d545d 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2282,7 +2282,12 @@ function Start-PSBootstrap { [switch]$BuildLinuxArm, [switch]$Force, [Parameter(Mandatory = $true)] - [ValidateSet("Package", "DotNet", "Both")] + # Package: Install dependencies for packaging tools (fpm, rpmbuild, WiX) + # DotNet: Install the .NET SDK + # Both: Package and DotNet scenarios + # Tools: Install .NET global tools (e.g., dotnet-format) + # All: Install all dependencies (packaging, .NET SDK, and tools) + [ValidateSet("Package", "DotNet", "Both", "Tools", "All")] [string]$Scenario = "Package" ) @@ -2402,7 +2407,7 @@ function Start-PSBootstrap { # Install [fpm](https://github.com/jordansissel/fpm) # Note: fpm is now only needed for DEB and macOS packages; RPM packages use rpmbuild directly - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { + if ($Scenario -in 'All', 'Both', 'Package') { # Install fpm on Debian-based systems, macOS, and Mariner (where DEB packages are built) if (($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) -or $environment.IsMacOS) { Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1" @@ -2410,7 +2415,7 @@ function Start-PSBootstrap { Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1" Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5" } - + # For RPM-based systems, ensure rpmbuild is available if ($environment.IsLinux -and ($environment.IsRedHatFamily -or $environment.IsSUSEFamily -or $environment.IsMariner)) { Write-Verbose -Verbose "Checking for rpmbuild..." @@ -2422,7 +2427,7 @@ function Start-PSBootstrap { } } - if ($Scenario -eq 'DotNet' -or $Scenario -eq 'Both') { + if ($Scenario -in 'All', 'Both', 'DotNet') { Write-Verbose -Verbose "Calling Find-Dotnet from Start-PSBootstrap" @@ -2479,6 +2484,19 @@ function Start-PSBootstrap { } } + if ($Scenario -in 'All', 'Tools') { + Write-Log -message "Installing .NET global tools" + + # Ensure dotnet is available + Find-Dotnet + + # Install dotnet-format + Write-Verbose -Verbose "Installing dotnet-format global tool" + Start-NativeExecution { + dotnet tool install --global dotnet-format + } + } + if ($env:TF_BUILD) { Write-Verbose -Verbose "--- Start - Capturing nuget sources" dotnet nuget list source --format detailed @@ -2646,6 +2664,63 @@ function Start-ResGen } } +function Add-PSEnvironmentPath { + <# + .SYNOPSIS + Adds a path to the process PATH and persists to GitHub Actions workflow if running in GitHub Actions + .PARAMETER Path + Path to add to PATH + .PARAMETER Prepend + If specified, prepends the path instead of appending + #> + param ( + [Parameter(Mandatory)] + [string]$Path, + + [switch]$Prepend + ) + + # Set in current process + if ($Prepend) { + $env:PATH = $Path + [IO.Path]::PathSeparator + $env:PATH + } else { + $env:PATH += [IO.Path]::PathSeparator + $Path + } + + # Persist to GitHub Actions workflow if running in GitHub Actions + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Verbose -Verbose "Adding $Path to GITHUB_PATH" + Add-Content -Path $env:GITHUB_PATH -Value $Path + } +} + +function Set-PSEnvironmentVariable { + <# + .SYNOPSIS + Sets an environment variable in the process and persists to GitHub Actions workflow if running in GitHub Actions + .PARAMETER Name + The name of the environment variable + .PARAMETER Value + The value of the environment variable + #> + param ( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$Value + ) + + # Set in current process + Set-Item -Path "env:$Name" -Value $Value + + # Persist to GitHub Actions workflow if running in GitHub Actions + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Verbose -Verbose "Setting $Name in GITHUB_ENV" + Add-Content -Path $env:GITHUB_ENV -Value "$Name=$Value" + } +} + function Find-Dotnet { param ( [switch] $SetDotnetRoot @@ -2676,25 +2751,40 @@ function Find-Dotnet { if ($dotnetCLIInstalledVersion -ne $chosenDotNetVersion) { Write-Warning "The 'dotnet' in the current path can't find SDK version ${dotnetCLIRequiredVersion}, prepending $dotnetPath to PATH." # Globally installed dotnet doesn't have the required SDK version, prepend the user local dotnet location - $env:PATH = $dotnetPath + [IO.Path]::PathSeparator + $env:PATH + Add-PSEnvironmentPath -Path $dotnetPath -Prepend if ($SetDotnetRoot) { Write-Verbose -Verbose "Setting DOTNET_ROOT to $dotnetPath" - $env:DOTNET_ROOT = $dotnetPath + Set-PSEnvironmentVariable -Name 'DOTNET_ROOT' -Value $dotnetPath } } elseif ($SetDotnetRoot) { Write-Verbose -Verbose "Expected dotnet version found, setting DOTNET_ROOT to $dotnetPath" - $env:DOTNET_ROOT = $dotnetPath + Set-PSEnvironmentVariable -Name 'DOTNET_ROOT' -Value $dotnetPath } } else { Write-Warning "Could not find 'dotnet', appending $dotnetPath to PATH." - $env:PATH += [IO.Path]::PathSeparator + $dotnetPath + Add-PSEnvironmentPath -Path $dotnetPath + + if ($SetDotnetRoot) { + Write-Verbose -Verbose "Setting DOTNET_ROOT to $dotnetPath" + Set-PSEnvironmentVariable -Name 'DOTNET_ROOT' -Value $dotnetPath + } } if (-not (precheck 'dotnet' "Still could not find 'dotnet', restoring PATH.")) { + # Give up, restore original PATH. There is nothing to persist since we didn't make a change. $env:PATH = $originalPath } + elseif ($SetDotnetRoot) { + # If we found dotnet, also add the global tools path to PATH + # Add .NET global tools to PATH when setting up the environment + $dotnetToolsPath = Join-Path $dotnetPath "tools" + if (Test-Path $dotnetToolsPath) { + Write-Verbose -Verbose "Adding .NET tools path to PATH: $dotnetToolsPath" + Add-PSEnvironmentPath -Path $dotnetToolsPath + } + } } <# From 4e694526d97ee32d282bfca9b87139079d36042f Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:10:29 +0100 Subject: [PATCH 068/255] Enable CA2022: Avoid inexact read with 'Stream.Read' (#25814) --- .globalconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.globalconfig b/.globalconfig index ed1d6c1a335..7395d94ea62 100644 --- a/.globalconfig +++ b/.globalconfig @@ -574,6 +574,10 @@ dotnet_diagnostic.CA2016.severity = suggestion # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021 dotnet_diagnostic.CA2021.severity = warning +# CA2022: Avoid inexact read with 'Stream.Read' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022 +dotnet_diagnostic.CA2022.severity = warning + # CA2100: Review SQL queries for security vulnerabilities # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100 dotnet_diagnostic.CA2100.severity = none From 82d59136b2a5ed1e35c97145c86b6ff2c54ec16a Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:54:03 +0100 Subject: [PATCH 069/255] Use `[initialsessionstate]` type accelerator (#25912) --- .../Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 | 4 ++-- .../Language/Classes/scripting.Classes.using.tests.ps1 | 2 +- .../Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 | 4 ++-- .../Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 | 2 +- .../Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 | 2 +- .../Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 | 2 +- .../Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index 4e5668071d7..6674697ca2f 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -833,7 +833,7 @@ class Derived : Base [Derived]::new().foo() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) @@ -929,7 +929,7 @@ class A : Foo.Bar return [A]::new() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [initialsessionstate]::CreateDefault() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) diff --git a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 index fb3778463da..d4e86e341b1 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -417,7 +417,7 @@ function foo() '@ # resolve name to absolute path $scriptToProcessPath = (Get-ChildItem $scriptToProcessPath).FullName - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [initialsessionstate]::CreateDefault() $iss.StartupScripts.Add($scriptToProcessPath) $ps = [powershell]::Create($iss) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 index c024f364c68..3325b21a9c4 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 @@ -16,7 +16,7 @@ Describe "Export-FormatData" -Tags "CI" { { $fd | Export-FormatData -Path $TESTDRIVE\allformat.ps1xml -IncludeScriptBlock - $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $sessionState = [initialsessionstate]::CreateDefault() $sessionState.Formats.Clear() $sessionState.Types.Clear() @@ -135,7 +135,7 @@ Describe "Export-FormatData" -Tags "CI" { $testfile = Join-Path -Path $TestDrive -ChildPath "$testfilename.ps1xml" Set-Content -Path $testfile -Value $xmlContent - $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $sessionState = [initialsessionstate]::CreateDefault() $sessionState.Formats.Clear() $sessionState.Types.Clear() diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 index b532d7523ef..0c15fe6d359 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 @@ -1901,7 +1901,7 @@ try Export-PSSession -Session $session -OutputModule $tempdir\Diag -CommandName New-Guid -AllowClobber > $null # Only the snapin Microsoft.PowerShell.Core is loaded - $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $ps = [PowerShell]::Create($iss) $result = $ps.AddScript(" & $tempdir\TestBug450687.ps1").Invoke() diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 index ba009de4b1b..96178fa13c0 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 @@ -36,7 +36,7 @@ Describe "Remove-TypeData DRT Unit Tests" -Tags "CI" { BeforeEach { $ps = [powershell]::Create() - $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace($iss) $rs.Open() $ps.Runspace = $rs diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 index d7cfa1f3204..0c2969dfb2a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 @@ -110,7 +110,7 @@ Describe "Update-FormatData with resources in CustomControls" -Tags "CI" { $templatePath = Join-Path $PSScriptRoot (Join-Path 'assets' 'UpdateFormatDataTests.format.ps1xml') $formatFilePath = Join-Path $TestDrive 'UpdateFormatDataTests.format.ps1xml' $ps = [powershell]::Create() - $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace($iss) $rs.Open() $ps.Runspace = $rs diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 index e2666ea6199..07a5f8ae2e6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 @@ -37,7 +37,7 @@ Describe "Update-TypeData basic functionality" -Tags "CI" { BeforeEach { $ps = [powershell]::Create() - $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace($iss) $rs.Open() $ps.Runspace = $rs From 70b0e8768cc875a69a85014826340ffdfee74436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:48:21 -0700 Subject: [PATCH 070/255] Bump actions/checkout from 4 to 5 (#26273) --- .github/workflows/verify-markdown-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify-markdown-links.yml b/.github/workflows/verify-markdown-links.yml index db9fb7e416a..b26539eb0cf 100644 --- a/.github/workflows/verify-markdown-links.yml +++ b/.github/workflows/verify-markdown-links.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Verify markdown links id: verify From f51860c1d14ee16d9055e0b818460dba0c935242 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:58:16 +0100 Subject: [PATCH 071/255] Remove unused timeout variable from RemoteHyperVTests class (#26297) --- test/xUnit/csharp/test_RemoteHyperV.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/xUnit/csharp/test_RemoteHyperV.cs b/test/xUnit/csharp/test_RemoteHyperV.cs index 27f7fb17375..c7cda754161 100644 --- a/test/xUnit/csharp/test_RemoteHyperV.cs +++ b/test/xUnit/csharp/test_RemoteHyperV.cs @@ -20,7 +20,6 @@ namespace PSTests.Sequential public class RemoteHyperVTests { private static ITestOutputHelper _output; - private static TimeSpan timeout = TimeSpan.FromSeconds(15); public RemoteHyperVTests(ITestOutputHelper output) { From 00efbe60767d2b3f91377c7eb6ac67d24c358600 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Sun, 26 Oct 2025 10:55:23 +0000 Subject: [PATCH 072/255] Enable CA1200: Avoid using cref tags with a prefix (#26298) --- .globalconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index 7395d94ea62..d60227efcbc 100644 --- a/.globalconfig +++ b/.globalconfig @@ -215,7 +215,7 @@ dotnet_diagnostic.CA1070.severity = warning # CA1200: Avoid using cref tags with a prefix # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1200 -dotnet_diagnostic.CA1200.severity = silent +dotnet_diagnostic.CA1200.severity = warning # CA1303: Do not pass literals as localized parameters # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1303 From fde5305e84d0865cc8090d8d943a5475bdaf360b Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 27 Oct 2025 04:12:53 +0000 Subject: [PATCH 073/255] Enable analyzers: Use char overload (#26301) --- .globalconfig | 12 ++++++++++++ .../commands/management/ControlPanelItemCommand.cs | 2 +- src/Microsoft.WSMan.Management/ConfigProvider.cs | 2 +- src/ResGen/Program.cs | 2 +- .../engine/NativeCommandParameterBinder.cs | 2 +- .../BenchmarkDotNet.Extensions/CommandLineOptions.cs | 2 +- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.globalconfig b/.globalconfig index d60227efcbc..21ecbc766aa 100644 --- a/.globalconfig +++ b/.globalconfig @@ -522,6 +522,18 @@ dotnet_diagnostic.CA1858.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860 dotnet_diagnostic.CA1860.severity = warning +# CA1865: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865 +dotnet_diagnostic.CA1865.severity = warning + +# CA1866: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1866 +dotnet_diagnostic.CA1866.severity = warning + +# CA1867: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1867 +dotnet_diagnostic.CA1867.severity = warning + # CA1868: Unnecessary call to 'Contains' for sets # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs index fffb36d2979..3734eb82b0c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs @@ -300,7 +300,7 @@ internal void GetCategoryMap() foreach (ShellFolderItem category in catItems) { string path = category.Path; - string catNum = path.Substring(path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase) + 1); + string catNum = path.Substring(path.LastIndexOf('\\') + 1); CategoryMap.Add(catNum, category.Name); } diff --git a/src/Microsoft.WSMan.Management/ConfigProvider.cs b/src/Microsoft.WSMan.Management/ConfigProvider.cs index f1fc3a36277..55141379faa 100644 --- a/src/Microsoft.WSMan.Management/ConfigProvider.cs +++ b/src/Microsoft.WSMan.Management/ConfigProvider.cs @@ -62,7 +62,7 @@ public sealed class WSManConfigProvider : NavigationCmdletProvider, ICmdletProvi string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path) { // Get the leaf node from the path for which help is requested. - int ChildIndex = path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase); + int ChildIndex = path.LastIndexOf('\\'); if (ChildIndex == -1) { // Means we are at host level, where no new-item is supported. Return empty string. diff --git a/src/ResGen/Program.cs b/src/ResGen/Program.cs index 3359226a99e..a69fd05e64f 100644 --- a/src/ResGen/Program.cs +++ b/src/ResGen/Program.cs @@ -52,7 +52,7 @@ public static void Main(string[] args) if (className.StartsWith("public.", StringComparison.InvariantCultureIgnoreCase)) { accessModifier = "public"; - className = className.Substring(className.IndexOf(".") + 1); + className = className.Substring(className.IndexOf('.') + 1); } string sourceCode = GetStronglyTypeCsFileForResx(resxPath, moduleName, className, accessModifier); diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index 4115546a758..2e62274ee53 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -161,7 +161,7 @@ internal string[] ArgumentList /// The value used with parameter. internal void AddToArgumentList(CommandParameterInternal parameter, string argument) { - if (parameter.ParameterNameSpecified && parameter.ParameterText.EndsWith(":")) + if (parameter.ParameterNameSpecified && parameter.ParameterText.EndsWith(':')) { if (argument != parameter.ParameterText) { diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs index aa60c7cc2e6..48856632317 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs @@ -37,7 +37,7 @@ public static List ParseAndRemoveStringsParameter(List argsList, if (parameterIndex + 1 < argsList.Count) { - while (parameterIndex + 1 < argsList.Count && !argsList[parameterIndex + 1].StartsWith("-")) + while (parameterIndex + 1 < argsList.Count && !argsList[parameterIndex + 1].StartsWith('-')) { // remove each filter string and stop when we get to the next argument flag parameterValue.Add(argsList[parameterIndex + 1]); From 0e1019ff31fc45ddcf1d766bded6b05ced74791b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:45:59 -0700 Subject: [PATCH 074/255] Bump actions/upload-artifact from 4 to 5 (#26309) --- .github/workflows/scorecards.yml | 2 +- .github/workflows/windows-packaging-reusable.yml | 2 +- .github/workflows/xunit-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index f612ed0e22a..35a9301eae4 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index a48e071e7a1..0afa912273c 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -80,7 +80,7 @@ jobs: - name: Upload Build Artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} path: | diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index c80ee40d039..efd05b76045 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -46,7 +46,7 @@ jobs: Write-Host "Completed xUnit test run." - name: Upload xUnit results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: always() with: name: ${{ inputs.test_results_artifact_name }} From ba02868d0fa1d724fcde39e612534d3db693eb39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:46:22 -0700 Subject: [PATCH 075/255] Bump github/codeql-action from 4.30.9 to 4.31.0 (#26308) --- .github/workflows/linux-ci.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 18defb738e2..a2a8fc4c79e 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -188,7 +188,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 + uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -214,7 +214,7 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 + uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 ready_to_merge: name: Linux ready to merge diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 35a9301eae4..ffd2f3f1420 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 + uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 with: sarif_file: results.sarif From be195a4c22c61321387e1460b5f58fa0a643d0bd Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:28:44 +0000 Subject: [PATCH 076/255] Avoid regex for exact word matching in `DscClassCache` (#26306) --- .../DscSupport/CimDSCParser.cs | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs index 417f1931c8a..283ec1bcad8 100644 --- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs +++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs @@ -525,9 +525,6 @@ public DscClassCacheEntry(DSCResourceRunAsCredential aDSCResourceRunAsCredential public static class DscClassCache { private const string InboxDscResourceModulePath = "WindowsPowershell\\v1.0\\Modules\\PsDesiredStateConfiguration"; - private const string reservedDynamicKeywords = "^(Synchronization|Certificate|IIS|SQL)$"; - - private const string reservedProperties = "^(Require|Trigger|Notify|Before|After|Subscribe)$"; private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DSC", "DSC Class Cache"); @@ -1264,13 +1261,6 @@ public static void ValidateInstanceText(string instanceText) parser.ValidateInstanceText(instanceText); } - private static bool IsMagicProperty(string propertyName) - { - return System.Text.RegularExpressions.Regex.Match(propertyName, - "^(ResourceId|SourceInfo|ModuleName|ModuleVersion|ConfigurationName)$", - System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success; - } - private static string GetFriendlyName(CimClass cimClass) { try @@ -1367,7 +1357,8 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi // // Skip all of the base, meta, registration and other classes that are not intended to be used directly by a script author // - if (System.Text.RegularExpressions.Regex.Match(keywordString, "^OMI_Base|^OMI_.*Registration", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (keywordString.StartsWith("OMI_Base", StringComparison.OrdinalIgnoreCase) || + (keywordString.StartsWith("OMI_", StringComparison.OrdinalIgnoreCase) && keywordString.IndexOf("Registration", 4, StringComparison.OrdinalIgnoreCase) >= 0)) { return null; } @@ -1383,7 +1374,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi }; // If it's one of reserved dynamic keyword, mark it - if (System.Text.RegularExpressions.Regex.Match(keywordString, reservedDynamicKeywords, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedDynamicKeyword(keywordString)) { keyword.IsReservedKeyword = true; } @@ -1441,7 +1432,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi } } // If it's one of our reserved properties, save it for error reporting - if (System.Text.RegularExpressions.Regex.Match(prop.Name, reservedProperties, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedProperty(prop.Name)) { keyword.HasReservedProperties = true; continue; @@ -1540,6 +1531,27 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi UpdateKnownRestriction(keyword); return keyword; + + static bool IsMagicProperty(string propertyName) => + string.Equals(propertyName, "ResourceId", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "SourceInfo", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ModuleName", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ModuleVersion", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ConfigurationName", StringComparison.OrdinalIgnoreCase); + + static bool IsReservedDynamicKeyword(string keyword) => + string.Equals(keyword, "Synchronization", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "Certificate", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "IIS", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "SQL", StringComparison.OrdinalIgnoreCase); + + static bool IsReservedProperty(string name) => + string.Equals(name, "Require", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Trigger", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Notify", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Before", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "After", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Subscribe", StringComparison.OrdinalIgnoreCase); } /// From d13cdadd9629a1364d522a5ded4293a16edfdd0b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:30:29 -0700 Subject: [PATCH 077/255] Fix linux_packaging job being skipped when only packaging files change (#26315) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .../infrastructure/path-filters/action.yml | 6 +- .../actions/test/linux-packaging/action.yml | 59 +------- .../build-and-packaging-steps.instructions.md | 127 ++++++++++++++++++ .github/workflows/linux-ci.yml | 1 - 4 files changed, 136 insertions(+), 57 deletions(-) create mode 100644 .github/instructions/build-and-packaging-steps.instructions.md diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml index 09ed7c22d17..ff07c4510fc 100644 --- a/.github/actions/infrastructure/path-filters/action.yml +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -88,12 +88,14 @@ runs: const globalConfigChanged = files.some(file => file.filename.startsWith('.globalconfig')) || files.some(file => file.filename.startsWith('nuget.config')) || files.some(file => file.filename.startsWith('global.json')); - const packagingChanged = files.some(file => + const packagingChanged = files.some(file => file.filename === '.github/workflows/windows-ci.yml' || + file.filename === '.github/workflows/linux-ci.yml' || file.filename.startsWith('assets/wix/') || file.filename === 'PowerShell.Common.props' || file.filename.match(/^src\/.*\.csproj$/) || file.filename.startsWith('test/packaging/windows/') || + file.filename.startsWith('test/packaging/linux/') || file.filename.startsWith('tools/packaging/') || file.filename.startsWith('tools/wix/') ) || @@ -112,7 +114,7 @@ runs: core.setOutput('globalConfigChanged', globalConfigChanged); core.setOutput('packagingChanged', packagingChanged); core.setOutput('source', source); - + - name: Capture outputs run: | diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index b7bbdf37185..374205f2a56 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -13,68 +13,19 @@ runs: with: global-json-file: ./global.json - - name: Download Build Artifacts - uses: actions/download-artifact@v4 - with: - name: build - path: "${{ runner.workspace }}/build" - - - name: Capture Artifacts Directory - continue-on-error: true - run: Get-ChildItem "${{ runner.workspace }}/build/*" -Recurse - shell: pwsh - - name: Bootstrap run: |- Import-Module ./build.psm1 Start-PSBootstrap -Scenario Package - Write-Verbose -Verbose "Start Sync-PSTags" - Sync-PSTags -AddRemoteIfMissing - Write-Verbose -Verbose "End Sync-PSTags" - shell: pwsh - - - name: Extract Build ZIP - run: |- - $destinationFolder = "${{ runner.workspace }}/bins" - $archiveFile = "${{ runner.workspace }}/build/build.zip" - - Write-Verbose "Extracting $archiveFile to $destinationFolder" -Verbose - New-Item -ItemType Directory -Path $destinationFolder -Force | Out-Null - Expand-Archive -Path $archiveFile -DestinationPath $destinationFolder -Force - shell: pwsh - - - name: Fix permissions - continue-on-error: true - run: |- - find "${{ runner.workspace }}/bins" -type d -exec chmod +rwx {} \; - find "${{ runner.workspace }}/bins" -type f -exec chmod +rw {} \; - shell: bash - - - name: Capture Extracted Build ZIP - continue-on-error: true - run: Get-ChildItem "${{ runner.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser shell: pwsh - - name: Create Packages - env: - BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ runner.workspace }}/packages + - name: Build and Package run: |- - # Create the artifacts staging directory - New-Item -ItemType Directory -Path "$env:BUILD_ARTIFACTSTAGINGDIRECTORY" -Force | Out-Null - - # Import packaging module to ensure RPM packaging changes are loaded - Import-Module ./build.psm1 -Force - Import-Module ./tools/packaging/packaging.psm1 -Force Import-Module ./tools/ci.psm1 - Restore-PSOptions -PSOptionsPath '${{ runner.workspace }}/build/psoptions.json' - $options = (Get-PSOptions) - $rootPath = '${{ runner.workspace }}/bins' - $originalRootPath = Split-Path -path $options.Output - $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) - $pwshPath = Join-Path -path $path -ChildPath 'pwsh' - chmod a+x $pwshPath - $options.Output = $pwshPath - Set-PSOptions $options + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag Invoke-CIFinish shell: pwsh diff --git a/.github/instructions/build-and-packaging-steps.instructions.md b/.github/instructions/build-and-packaging-steps.instructions.md new file mode 100644 index 00000000000..934b1539593 --- /dev/null +++ b/.github/instructions/build-and-packaging-steps.instructions.md @@ -0,0 +1,127 @@ +--- +applyTo: + - ".github/actions/**/*.yml" + - ".github/workflows/**/*.yml" +--- + +# Build and Packaging Steps Pattern + +## Important Rule + +**Build and packaging must run in the same step OR you must save and restore PSOptions between steps.** + +## Why This Matters + +When `Start-PSBuild` runs, it creates PSOptions that contain build configuration details (runtime, configuration, output path, etc.). The packaging functions like `Start-PSPackage` and `Invoke-CIFinish` rely on these PSOptions to know where the build output is located and how it was built. + +GitHub Actions steps run in separate PowerShell sessions. This means PSOptions from one step are not available in the next step. + +## Pattern 1: Combined Build and Package (Recommended) + +Run build and packaging in the same step to keep PSOptions in memory: + +```yaml +- name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh +``` + +**Benefits:** +- Simpler code +- No need for intermediate files +- PSOptions automatically available to packaging + +## Pattern 2: Separate Steps with Save/Restore + +If you must separate build and packaging into different steps: + +```yaml +- name: Build PowerShell + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Save-PSOptions -PSOptionsPath "${{ runner.workspace }}/psoptions.json" + shell: pwsh + +- name: Create Packages + run: |- + Import-Module ./tools/ci.psm1 + Restore-PSOptions -PSOptionsPath "${{ runner.workspace }}/psoptions.json" + Invoke-CIFinish + shell: pwsh +``` + +**When to use:** +- When you need to run other steps between build and packaging +- When build and packaging require different permissions or environments + +## Common Mistakes + +### ❌ Incorrect: Separate steps without save/restore + +```yaml +- name: Build PowerShell + run: |- + Start-PSBuild -Configuration 'Release' + shell: pwsh + +- name: Create Packages + run: |- + Invoke-CIFinish # ❌ FAILS: PSOptions not available + shell: pwsh +``` + +### ❌ Incorrect: Using artifacts without PSOptions + +```yaml +- name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + name: build + +- name: Create Packages + run: |- + Invoke-CIFinish # ❌ FAILS: PSOptions not restored + shell: pwsh +``` + +## Related Functions + +- `Start-PSBuild` - Builds PowerShell and sets PSOptions +- `Save-PSOptions` - Saves PSOptions to a JSON file +- `Restore-PSOptions` - Loads PSOptions from a JSON file +- `Get-PSOptions` - Gets current PSOptions +- `Set-PSOptions` - Sets PSOptions +- `Start-PSPackage` - Creates packages (requires PSOptions) +- `Invoke-CIFinish` - Calls packaging (requires PSOptions on Linux/macOS) + +## Examples + +### Linux Packaging Action + +```yaml +- name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh +``` + +### Windows Packaging Workflow + +```yaml +- name: Build and Package + run: | + Import-Module .\tools\ci.psm1 + Invoke-CIFinish -Runtime ${{ matrix.runtimePrefix }}-${{ matrix.architecture }} -channel ${{ matrix.channel }} + shell: pwsh +``` + +Note: `Invoke-CIFinish` for Windows includes both build and packaging in its logic when `Stage` contains 'Build'. diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index a2a8fc4c79e..55cf112ea43 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -233,7 +233,6 @@ jobs: linux_packaging: name: Linux Packaging needs: - - ci_build - changes if: ${{ needs.changes.outputs.packagingChanged == 'true' }} runs-on: ubuntu-latest From 5e5e17766be554173b18ae8faf243b6d905ce2ff Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:23:38 -0400 Subject: [PATCH 078/255] Refactor analyze job to reusable workflow and enable on Windows CI (#26322) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .github/workflows/analyze-reusable.yml | 76 ++++++++++++++++++++++++++ .github/workflows/linux-ci.yml | 63 +++------------------ .github/workflows/windows-ci.yml | 12 ++++ tools/ci.psm1 | 7 ++- 4 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/analyze-reusable.yml diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml new file mode 100644 index 00000000000..1797e2234a6 --- /dev/null +++ b/.github/workflows/analyze-reusable.yml @@ -0,0 +1,76 @@ +name: CodeQL Analysis (Reusable) + +on: + workflow_call: + inputs: + runner_os: + description: 'Runner OS for CodeQL analysis' + type: string + required: false + default: ubuntu-latest + +permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/analyze to upload SARIF results + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + +jobs: + analyze: + name: Analyze + runs-on: ${{ inputs.runner_os }} + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['csharp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: '0' + + - uses: actions/setup-dotnet@v5 + with: + global-json-file: ./global.json + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - run: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + name: Capture Environment + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + name: Bootstrap + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIBuild -Configuration 'StaticAnalysis' + name: Build + shell: pwsh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 55cf112ea43..1650638be8e 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -158,63 +158,16 @@ jobs: test_results_artifact_name: testResults-xunit analyze: - permissions: - actions: read # for github/codeql-action/init to get workflow details - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/analyze to upload SARIF results - name: Analyze - runs-on: ubuntu-latest + name: CodeQL Analysis needs: changes if: ${{ needs.changes.outputs.source == 'true' }} - - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['csharp'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection - - steps: - - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - fetch-depth: '0' - - - uses: actions/setup-dotnet@v5 - with: - global-json-file: ./global.json - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - - run: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - name: Capture Environment - shell: pwsh - - - run: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall -SkipUser - name: Bootstrap - shell: pwsh - - - run: | - Import-Module .\tools\ci.psm1 - Invoke-CIBuild - name: Build - shell: pwsh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: ./.github/workflows/analyze-reusable.yml + permissions: + actions: read + contents: read + security-events: write + with: + runner_os: ubuntu-latest ready_to_merge: name: Linux ready to merge diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index e3fbe7f7185..f33f1a3f589 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -156,6 +156,17 @@ jobs: with: runner_os: windows-latest test_results_artifact_name: testResults-xunit + analyze: + name: CodeQL Analysis + needs: changes + if: ${{ needs.changes.outputs.source == 'true' }} + uses: ./.github/workflows/analyze-reusable.yml + permissions: + actions: read + contents: read + security-events: write + with: + runner_os: windows-latest windows_packaging: name: Windows Packaging needs: @@ -170,6 +181,7 @@ jobs: - windows_test_elevated_others - windows_test_unelevated_ci - windows_test_unelevated_others + - analyze - windows_packaging if: always() uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 9e95e68c843..44651c26109 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -101,6 +101,11 @@ function Invoke-CIFull # Implements the CI 'build_script' step function Invoke-CIBuild { + param( + [ValidateSet('Debug', 'Release', 'CodeCoverage', 'StaticAnalysis')] + [string]$Configuration = 'Release' + ) + $releaseTag = Get-ReleaseTag # check to be sure our test tags are correct $result = Get-PesterTag @@ -115,7 +120,7 @@ function Invoke-CIBuild Start-PSBuild -Configuration 'CodeCoverage' -PSModuleRestore -CI -ReleaseTag $releaseTag } - Start-PSBuild -PSModuleRestore -Configuration 'Release' -CI -ReleaseTag $releaseTag -UseNuGetOrg + Start-PSBuild -PSModuleRestore -Configuration $Configuration -CI -ReleaseTag $releaseTag -UseNuGetOrg Save-PSOptions $options = (Get-PSOptions) From 47e8e900ab5e156405a926c4e9916f029f90d290 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:09:26 -0700 Subject: [PATCH 079/255] Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26268) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk --- .../build-configuration-guide.instructions.md | 48 ++- .../start-native-execution.instructions.md | 149 ++++++++ .github/workflows/macos-ci.yml | 55 ++- docs/maintainers/releasing.md | 17 +- tools/packaging/packaging.psm1 | 360 ++++++++++++++---- .../releaseTests/macOSPackage.tests.ps1 | 162 ++++++++ 6 files changed, 705 insertions(+), 86 deletions(-) create mode 100644 .github/instructions/start-native-execution.instructions.md create mode 100644 tools/packaging/releaseTests/macOSPackage.tests.ps1 diff --git a/.github/instructions/build-configuration-guide.instructions.md b/.github/instructions/build-configuration-guide.instructions.md index 848aacef496..56844777d95 100644 --- a/.github/instructions/build-configuration-guide.instructions.md +++ b/.github/instructions/build-configuration-guide.instructions.md @@ -35,13 +35,15 @@ applyTo: ### For Release/Packaging -**Use: Release with version tag** +**Use: Release with version tag and public NuGet feeds** ```yaml - name: Build for Release shell: pwsh run: | + Import-Module ./build.psm1 Import-Module ./tools/ci.psm1 + Switch-PSNugetConfig -Source Public $releaseTag = Get-ReleaseTag Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag ``` @@ -51,6 +53,11 @@ applyTo: - No debug symbols (smaller size) - Production-ready +**Why Switch-PSNugetConfig -Source Public:** +- Switches NuGet package sources to public feeds (nuget.org and public Azure DevOps feeds) +- Required for CI/CD environments that don't have access to private feeds +- Uses publicly available packages instead of Microsoft internal feeds + ### For Code Coverage **Use: CodeCoverage configuration** @@ -101,3 +108,42 @@ src/powershell-win-core/bin/Debug///publish/ 3. Match configuration to purpose 4. Use `-CI` only when needed 5. Always specify `-ReleaseTag` for release or packaging builds +6. Use `Switch-PSNugetConfig -Source Public` in CI/CD for release builds + +## NuGet Feed Configuration + +### Switch-PSNugetConfig + +The `Switch-PSNugetConfig` function in `build.psm1` manages NuGet package source configuration. + +**Available Sources:** + +- **Public**: Uses public feeds (nuget.org and public Azure DevOps feeds) + - Required for: CI/CD environments, public builds, packaging + - Does not require authentication + +- **Private**: Uses internal PowerShell team feeds + - Required for: Internal development with preview packages + - Requires authentication credentials + +- **NuGetOnly**: Uses only nuget.org + - Required for: Minimal dependency scenarios + +**Usage:** + +```powershell +# Switch to public feeds (most common for CI/CD) +Switch-PSNugetConfig -Source Public + +# Switch to private feeds with authentication +Switch-PSNugetConfig -Source Private -UserName $userName -ClearTextPAT $pat + +# Switch to nuget.org only +Switch-PSNugetConfig -Source NuGetOnly +``` + +**When to Use:** + +- **Always use `-Source Public`** before building in CI/CD workflows +- Use before any build that will create packages for distribution +- Use in forks or environments without access to Microsoft internal feeds diff --git a/.github/instructions/start-native-execution.instructions.md b/.github/instructions/start-native-execution.instructions.md new file mode 100644 index 00000000000..347e496b3bf --- /dev/null +++ b/.github/instructions/start-native-execution.instructions.md @@ -0,0 +1,149 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# Using Start-NativeExecution for Native Command Execution + +## Purpose + +`Start-NativeExecution` is the standard function for executing native commands (external executables) in PowerShell scripts within this repository. It provides consistent error handling and better diagnostics when native commands fail. + +## When to Use + +Use `Start-NativeExecution` whenever you need to: +- Execute external commands (e.g., `git`, `dotnet`, `pkgbuild`, `productbuild`, `fpm`, `rpmbuild`) +- Ensure proper exit code checking +- Get better error messages with caller information +- Handle verbose output on error + +## Basic Usage + +```powershell +Start-NativeExecution { + git clone https://github.com/PowerShell/PowerShell.git +} +``` + +## With Parameters + +Use backticks for line continuation within the script block: + +```powershell +Start-NativeExecution { + pkgbuild --root $pkgRoot ` + --identifier $pkgIdentifier ` + --version $Version ` + --scripts $scriptsDir ` + $outputPath +} +``` + +## Common Parameters + +### -VerboseOutputOnError + +Captures command output and displays it only if the command fails: + +```powershell +Start-NativeExecution -VerboseOutputOnError { + dotnet build --configuration Release +} +``` + +### -IgnoreExitcode + +Allows the command to fail without throwing an exception: + +```powershell +Start-NativeExecution -IgnoreExitcode { + git diff --exit-code # Returns 1 if differences exist +} +``` + +## Availability + +The function is defined in `tools/buildCommon/startNativeExecution.ps1` and is available in: +- `build.psm1` (dot-sourced automatically) +- `tools/packaging/packaging.psm1` (dot-sourced automatically) +- Test modules that include `HelpersCommon.psm1` + +To use in other scripts, dot-source the function: + +```powershell +. "$PSScriptRoot/../buildCommon/startNativeExecution.ps1" +``` + +## Error Handling + +When a native command fails (non-zero exit code), `Start-NativeExecution`: +1. Captures the exit code +2. Identifies the calling location (file and line number) +3. Throws a descriptive error with full context + +Example error message: +``` +Execution of {git clone ...} by /path/to/script.ps1: line 42 failed with exit code 1 +``` + +## Examples from the Codebase + +### Git Operations +```powershell +Start-NativeExecution { + git fetch --tags --quiet upstream +} +``` + +### Build Operations +```powershell +Start-NativeExecution -VerboseOutputOnError { + dotnet publish --configuration Release +} +``` + +### Packaging Operations +```powershell +Start-NativeExecution -VerboseOutputOnError { + pkgbuild --root $pkgRoot --identifier $pkgId --version $version $outputPath +} +``` + +### Permission Changes +```powershell +Start-NativeExecution { + find $staging -type d | xargs chmod 755 + find $staging -type f | xargs chmod 644 +} +``` + +## Anti-Patterns + +**Don't do this:** +```powershell +& somecommand $args +if ($LASTEXITCODE -ne 0) { + throw "Command failed" +} +``` + +**Do this instead:** +```powershell +Start-NativeExecution { + somecommand $args +} +``` + +## Best Practices + +1. **Always use Start-NativeExecution** for native commands to ensure consistent error handling +2. **Use -VerboseOutputOnError** for commands with useful diagnostic output +3. **Use backticks for readability** when commands have multiple arguments +4. **Don't capture output unnecessarily** - let the function handle it +5. **Use -IgnoreExitcode sparingly** - only when non-zero exit codes are expected and acceptable + +## Related Documentation + +- Source: `tools/buildCommon/startNativeExecution.ps1` +- Blog post: https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/ diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index b0242a6d212..95edb2b8d96 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -153,7 +153,7 @@ jobs: runner_os: macos-15-large test_results_artifact_name: testResults-xunit PackageMac-macos_packaging: - name: macOS packaging (bootstrap only) + name: macOS packaging and testing needs: - changes if: ${{ needs.changes.outputs.source == 'true' }} @@ -162,12 +162,63 @@ jobs: steps: - name: checkout uses: actions/checkout@v5 + with: + fetch-depth: 1000 + - uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json - name: Bootstrap packaging - if: success() || failure() + if: success() run: |- import-module ./build.psm1 start-psbootstrap -Scenario package shell: pwsh + - name: Build PowerShell and Create macOS package + if: success() + run: |- + import-module ./build.psm1 + import-module ./tools/ci.psm1 + import-module ./tools/packaging/packaging.psm1 + Switch-PSNugetConfig -Source Public + Sync-PSTags -AddRemoteIfMissing + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration Release -PSModuleRestore -ReleaseTag $releaseTag + $macOSRuntime = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'osx-arm64' } else { 'osx-x64' } + Start-PSPackage -Type osxpkg -ReleaseTag $releaseTag -MacOSRuntime $macOSRuntime -SkipReleaseChecks + shell: pwsh + - name: Test package contents + if: success() + run: |- + $env:PACKAGE_FOLDER = Get-Location + $testResultsPath = Join-Path $env:RUNNER_WORKSPACE "testResults" + if (-not (Test-Path $testResultsPath)) { + New-Item -ItemType Directory -Path $testResultsPath -Force | Out-Null + } + Import-Module Pester + $pesterConfig = New-PesterConfiguration + $pesterConfig.Run.Path = './tools/packaging/releaseTests/macOSPackage.tests.ps1' + $pesterConfig.Run.PassThru = $true + $pesterConfig.Output.Verbosity = 'Detailed' + $pesterConfig.TestResult.Enabled = $true + $pesterConfig.TestResult.OutputFormat = 'NUnitXml' + $pesterConfig.TestResult.OutputPath = Join-Path $testResultsPath "macOSPackage.xml" + $result = Invoke-Pester -Configuration $pesterConfig + if ($result.FailedCount -gt 0) { + throw "Package validation failed with $($result.FailedCount) failed test(s)" + } + shell: pwsh + - name: Publish and Upload Pester Test Results + if: always() + uses: "./.github/actions/test/process-pester-results" + with: + name: "macOSPackage" + testResultsFolder: "${{ runner.workspace }}/testResults" + - name: Upload package artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: macos-package + path: "*.pkg" ready_to_merge: name: macos ready to merge needs: diff --git a/docs/maintainers/releasing.md b/docs/maintainers/releasing.md index 5aae87582c9..3562962e68f 100644 --- a/docs/maintainers/releasing.md +++ b/docs/maintainers/releasing.md @@ -72,11 +72,18 @@ The output of `Start-PSBuild` includes a `powershell.exe` executable which can s #### Linux / macOS The `Start-PSPackage` function delegates to `New-UnixPackage`. -It relies on the [Effing Package Management][fpm] project, -which makes building packages for any (non-Windows) platform a breeze. -Similarly, the PowerShell man-page is generated from the Markdown-like file + +For **Linux** (Debian-based distributions), it relies on the [Effing Package Management][fpm] project, +which makes building packages a breeze. + +For **macOS**, it uses native packaging tools (`pkgbuild` and `productbuild`) from Xcode Command Line Tools, +eliminating the need for Ruby or fpm. + +For **Linux** (Red Hat-based distributions), it uses `rpmbuild` directly. + +The PowerShell man-page is generated from the Markdown-like file [`assets/pwsh.1.ronn`][man] using [Ronn][]. -The function `Start-PSBootstrap -Package` will install both these tools. +The function `Start-PSBootstrap -Package` will install these tools. To modify any property of the packages, edit the `New-UnixPackage` function. Please also refer to the function for details on the package properties @@ -131,7 +138,7 @@ Without `-Name` specified, the primary `powershell` package will instead be created. [fpm]: https://github.com/jordansissel/fpm -[man]: ../../assets/pwsh.1.ronn +[man]: ../../assets/manpage/pwsh.1.ronn [ronn]: https://github.com/rtomayko/ronn ### Build and Packaging Examples diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 435fe2d49bc..257b3694510 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +. "$PSScriptRoot\..\buildCommon\startNativeExecution.ps1" + $Environment = Get-EnvironmentInformation $RepoRoot = (Resolve-Path -Path "$PSScriptRoot/../..").Path @@ -1197,19 +1199,7 @@ function New-UnixPackage { # Generate After Install and After Remove scripts $AfterScriptInfo = New-AfterScripts -Link $Link -Distribution $DebDistro -Destination $Destination - # there is a weird bug in fpm - # if the target of the powershell symlink exists, `fpm` aborts - # with a `utime` error on macOS. - # so we move it to make symlink broken - # refers to executable, does not vary by channel - $symlink_dest = "$Destination/pwsh" - $hack_dest = "./_fpm_symlink_hack_powershell" - if ($Environment.IsMacOS) { - if (Test-Path $symlink_dest) { - Write-Warning "Move $symlink_dest to $hack_dest (fpm utime bug)" - Start-NativeExecution ([ScriptBlock]::Create("$sudo mv $symlink_dest $hack_dest")) - } - } + # Note: The fpm symlink workaround is no longer needed with native macOS packaging tools # Generate gzip of man file $ManGzipInfo = New-ManGzip -IsPreview:$IsPreview -IsLTS:$LTS @@ -1329,8 +1319,38 @@ function New-UnixPackage { throw } } + } elseif ($Type -eq 'osxpkg') { + # Use native macOS packaging tools + if ($PSCmdlet.ShouldProcess("Create macOS package with pkgbuild/productbuild")) { + Write-Log "Creating macOS package with native tools..." + + $macPkgArgs = @{ + Name = $Name + Version = $packageVersion + Iteration = $Iteration + Staging = $Staging + Destination = $Destination + ManGzipFile = $ManGzipInfo.GzipFile + ManDestination = $ManGzipInfo.ManFile + LinkInfo = $Links + AfterInstallScript = $AfterScriptInfo.AfterInstallScript + AppsFolder = $AppsFolder + HostArchitecture = $HostArchitecture + CurrentLocation = $CurrentLocation + } + + try { + $packageFile = New-MacOSPackage @macPkgArgs + $Output = @("Created package {:path=>""$($packageFile.Name)""}") + } + catch { + Write-Verbose -Message "!!!Handling error in macOS packaging!!!" -Verbose -ErrorAction SilentlyContinue + Get-Error -InputObject $_ + throw + } + } } else { - # Use fpm for DEB and macOS packages + # Use fpm for DEB packages $Arguments = @() $Arguments += Get-FpmArguments ` @@ -1372,12 +1392,6 @@ function New-UnixPackage { { Clear-MacOSLauncher } - - # this is continuation of a fpm hack for a weird bug - if (Test-Path $hack_dest) { - Write-Warning "Move $hack_dest to $symlink_dest (fpm utime bug)" - Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo mv $hack_dest $symlink_dest")) -VerboseOutputOnError - } } # Clean up rpmbuild directory if it was created @@ -1401,12 +1415,9 @@ function New-UnixPackage { # Magic to get path output $createdPackage = Get-Item (Join-Path $CurrentLocation (($Output[-1] -split ":path=>")[-1] -replace '["{}]')) - if ($Environment.IsMacOS) { - if ($PSCmdlet.ShouldProcess("Add distribution information and Fix PackageName")) - { - $createdPackage = New-MacOsDistributionPackage -FpmPackage $createdPackage -HostArchitecture $HostArchitecture -IsPreview:$IsPreview - } - } + # For macOS with native tools, the package is already in the correct format + # For macOS with fpm (no longer used), we would need New-MacOsDistributionPackage + # For other platforms, the package name from fpm/rpmbuild is sufficient if (Test-Path $createdPackage) { @@ -1451,14 +1462,27 @@ Function New-LinkInfo function New-MacOsDistributionPackage { + [CmdletBinding(SupportsShouldProcess=$true)] param( - [Parameter(Mandatory,HelpMessage='The FileInfo of the file created by FPM')] - [System.IO.FileInfo]$FpmPackage, + [Parameter(Mandatory,HelpMessage='The FileInfo of the component package')] + [System.IO.FileInfo]$ComponentPackage, + + [Parameter(Mandatory,HelpMessage='Package name for the output file')] + [string]$PackageName, + + [Parameter(Mandatory,HelpMessage='Package version')] + [string]$Version, + + [Parameter(Mandatory,HelpMessage='Output directory for the final package')] + [string]$OutputDirectory, [Parameter(HelpMessage='x86_64 for Intel or arm64 for Apple Silicon')] [ValidateSet("x86_64", "arm64")] [string] $HostArchitecture = "x86_64", + [Parameter(HelpMessage='Package identifier')] + [string]$PackageIdentifier, + [Switch] $IsPreview ) @@ -1467,64 +1491,81 @@ function New-MacOsDistributionPackage throw 'New-MacOsDistributionPackage is only supported on macOS!' } - $packageName = Split-Path -Leaf -Path $FpmPackage - # Create a temp directory to store the needed files $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Path $tempDir -Force > $null $resourcesDir = Join-Path -Path $tempDir -ChildPath 'resources' New-Item -ItemType Directory -Path $resourcesDir -Force > $null - #Copy background file to temp directory + + # Copy background file to temp directory $backgroundFile = "$RepoRoot/assets/macDialog.png" - Copy-Item -Path $backgroundFile -Destination $resourcesDir - # Move the current package to the temp directory - $tempPackagePath = Join-Path -Path $tempDir -ChildPath $packageName - Move-Item -Path $FpmPackage -Destination $tempPackagePath -Force - - # Add the OS information to the macOS package file name. - $packageExt = [System.IO.Path]::GetExtension($FpmPackage.Name) - - # get the package name from fpm without the extension, but replace powershell-preview at the beginning of the name with powershell. - $packageNameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($FpmPackage.Name) -replace '^powershell\-preview' , 'powershell' - - $newPackageName = "{0}-{1}{2}" -f $packageNameWithoutExt, $script:Options.Runtime, $packageExt - $newPackagePath = Join-Path $FpmPackage.DirectoryName $newPackageName - - # -Force is not deleting the NewName if it exists, so delete it if it does - if ($Force -and (Test-Path -Path $newPackagePath)) - { - Remove-Item -Force $newPackagePath + if (Test-Path $backgroundFile) { + Copy-Item -Path $backgroundFile -Destination $resourcesDir -Force } + + # Copy the component package to temp directory + $componentFileName = Split-Path -Leaf -Path $ComponentPackage + $tempComponentPath = Join-Path -Path $tempDir -ChildPath $componentFileName + Copy-Item -Path $ComponentPackage -Destination $tempComponentPath -Force # Create the distribution xml $distributionXmlPath = Join-Path -Path $tempDir -ChildPath 'powershellDistribution.xml' - $packageId = Get-MacOSPackageId -IsPreview:$IsPreview.IsPresent + # Get package ID if not provided + if (-not $PackageIdentifier) { + $PackageIdentifier = Get-MacOSPackageId -IsPreview:$IsPreview.IsPresent + } + # Minimum OS version + $minOSVersion = "11.0" # macOS Big Sur minimum + # format distribution template with: # 0 - title # 1 - version - # 2 - package path + # 2 - package path (component package filename) # 3 - minimum os version # 4 - Package Identifier # 5 - host architecture (x86_64 for Intel or arm64 for Apple Silicon) - $PackagingStrings.OsxDistributionTemplate -f "PowerShell - $packageVersion", $packageVersion, $packageName, '10.14', $packageId, $HostArchitecture | Out-File -Encoding ascii -FilePath $distributionXmlPath -Force + $PackagingStrings.OsxDistributionTemplate -f $PackageName, $Version, $componentFileName, $minOSVersion, $PackageIdentifier, $HostArchitecture | Out-File -Encoding utf8 -FilePath $distributionXmlPath -Force - Write-Log "Applying distribution.xml to package..." - Push-Location $tempDir - try - { - # productbuild is an xcode command line tool, and those tools are installed when you install brew - Start-NativeExecution -sb {productbuild --distribution $distributionXmlPath --resources $resourcesDir $newPackagePath} -VerboseOutputOnError + # Build final package path + $finalPackagePath = Join-Path $OutputDirectory "$PackageName-$Version-osx-$HostArchitecture.pkg" + + # Remove existing package if it exists + if (Test-Path $finalPackagePath) { + Write-Warning "Removing existing package: $finalPackagePath" + Remove-Item $finalPackagePath -Force } - finally - { - Pop-Location - Remove-Item -Path $tempDir -Recurse -Force + + if ($PSCmdlet.ShouldProcess("Build product package with productbuild")) { + Write-Log "Applying distribution.xml to package..." + Push-Location $tempDir + try + { + # productbuild is an xcode command line tool + Start-NativeExecution -VerboseOutputOnError { + productbuild --distribution $distributionXmlPath ` + --package-path $tempDir ` + --resources $resourcesDir ` + $finalPackagePath + } + + if (Test-Path $finalPackagePath) { + Write-Log "Successfully created macOS package: $finalPackagePath" + } + else { + throw "Package was not created at expected location: $finalPackagePath" + } + } + finally + { + Pop-Location + Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } } - return (Get-Item $newPackagePath) + return (Get-Item $finalPackagePath) } Class LinkInfo @@ -1692,6 +1733,157 @@ cp $ManGzipFile `$RPM_BUILD_ROOT$ManDestination return $specContent } +function New-MacOSPackage +{ + [CmdletBinding(SupportsShouldProcess=$true)] + param( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$Version, + + [Parameter(Mandatory)] + [string]$Iteration, + + [Parameter(Mandatory)] + [string]$Staging, + + [Parameter(Mandatory)] + [string]$Destination, + + [Parameter(Mandatory)] + [string]$ManGzipFile, + + [Parameter(Mandatory)] + [string]$ManDestination, + + [Parameter(Mandatory)] + [LinkInfo[]]$LinkInfo, + + [Parameter(Mandatory)] + [string]$AfterInstallScript, + + [Parameter(Mandatory)] + [string]$AppsFolder, + + [Parameter(Mandatory)] + [string]$HostArchitecture, + + [string]$CurrentLocation = (Get-Location) + ) + + Write-Log "Creating macOS package using pkgbuild and productbuild..." + + # Create a temporary directory for package building + $tempRoot = New-TempFolder + $componentPkgPath = Join-Path $tempRoot "component.pkg" + $scriptsDir = Join-Path $tempRoot "scripts" + $resourcesDir = Join-Path $tempRoot "resources" + $distributionFile = Join-Path $tempRoot "distribution.xml" + + try { + # Create scripts directory + New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null + + # Copy and prepare the postinstall script + $postInstallPath = Join-Path $scriptsDir "postinstall" + Copy-Item -Path $AfterInstallScript -Destination $postInstallPath -Force + Start-NativeExecution { + chmod 755 $postInstallPath + } + + # Create a temporary directory for the package root + $pkgRoot = Join-Path $tempRoot "pkgroot" + New-Item -ItemType Directory -Path $pkgRoot -Force | Out-Null + + # Copy staging files to destination path in package root + $destInPkg = Join-Path $pkgRoot $Destination + New-Item -ItemType Directory -Path $destInPkg -Force | Out-Null + Write-Verbose "Copying staging files from $Staging to $destInPkg" -Verbose + Copy-Item -Path "$Staging/*" -Destination $destInPkg -Recurse -Force + + # Create man page directory structure + $manDir = Join-Path $pkgRoot (Split-Path $ManDestination -Parent) + New-Item -ItemType Directory -Path $manDir -Force | Out-Null + Copy-Item -Path $ManGzipFile -Destination (Join-Path $pkgRoot $ManDestination) -Force + + # Create symlinks in package root + # The LinkInfo contains Source (a temp file that IS a symlink) and Destination (where to install it) + foreach ($link in $LinkInfo) { + $linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent) + New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null + $finalLinkPath = Join-Path $pkgRoot $link.Destination + + Write-Verbose "Creating symlink at $finalLinkPath" -Verbose + + # Remove if exists + if (Test-Path $finalLinkPath) { + Remove-Item $finalLinkPath -Force + } + + # Get the target of the original symlink and recreate it in the package root + if (Test-Path $link.Source) { + $linkTarget = (Get-Item $link.Source).Target + if ($linkTarget) { + Write-Verbose "Creating symlink to target: $linkTarget" -Verbose + New-Item -ItemType SymbolicLink -Path $finalLinkPath -Target $linkTarget -Force | Out-Null + } else { + Write-Warning "Could not determine target for symlink at $($link.Source), copying file instead" + Copy-Item -Path $link.Source -Destination $finalLinkPath -Force + } + } else { + Write-Warning "Source symlink $($link.Source) does not exist" + } + } + + # Copy launcher app folder if provided + if ($AppsFolder) { + $appsInPkg = Join-Path $pkgRoot "Applications" + New-Item -ItemType Directory -Path $appsInPkg -Force | Out-Null + Write-Verbose "Copying launcher app from $AppsFolder to $appsInPkg" -Verbose + Copy-Item -Path "$AppsFolder/*" -Destination $appsInPkg -Recurse -Force + } + + # Build the component package using pkgbuild + $pkgIdentifier = Get-MacOSPackageId -IsPreview:($Name -like '*-preview') + + if ($PSCmdlet.ShouldProcess("Build component package with pkgbuild")) { + Write-Log "Running pkgbuild to create component package..." + + Start-NativeExecution -VerboseOutputOnError { + pkgbuild --root $pkgRoot ` + --identifier $pkgIdentifier ` + --version $Version ` + --scripts $scriptsDir ` + --install-location "/" ` + $componentPkgPath + } + + Write-Verbose "Component package created: $componentPkgPath" -Verbose + } + + # Create the final distribution package using the refactored function + $distributionPackage = New-MacOsDistributionPackage ` + -ComponentPackage (Get-Item $componentPkgPath) ` + -PackageName $Name ` + -Version $Version ` + -OutputDirectory $CurrentLocation ` + -HostArchitecture $HostArchitecture ` + -PackageIdentifier $pkgIdentifier ` + -IsPreview:($Name -like '*-preview') + + return $distributionPackage + } + finally { + # Clean up temporary directory + if (Test-Path $tempRoot) { + Write-Verbose "Cleaning up temporary directory: $tempRoot" -Verbose + Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + function Get-FpmArguments { param( @@ -1905,6 +2097,7 @@ function Get-PackageDependencies function Test-Dependencies { # Note: RPM packages no longer require fpm; they use rpmbuild directly + # macOS packages use pkgbuild and productbuild from Xcode Command Line Tools # DEB packages still use fpm $Dependencies = @() @@ -1913,22 +2106,31 @@ function Test-Dependencies $Dependencies += "fpm" } + # Check for macOS packaging tools + if ($Environment.IsMacOS) { + $Dependencies += "pkgbuild" + $Dependencies += "productbuild" + } + foreach ($Dependency in $Dependencies) { if (!(precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { - # These tools are not added to the path automatically on OpenSUSE 13.2 - # try adding them to the path and re-tesing first - [string] $gemsPath = $null - [string] $depenencyPath = $null - $gemsPath = Get-ChildItem -Path /usr/lib64/ruby/gems | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName - if ($gemsPath) { - $depenencyPath = Get-ChildItem -Path (Join-Path -Path $gemsPath -ChildPath "gems" -AdditionalChildPath $Dependency) -Recurse | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty DirectoryName - $originalPath = $env:PATH - $env:PATH = $ENV:PATH +":" + $depenencyPath - if ((precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { - continue - } - else { - $env:PATH = $originalPath + # For Debian systems, try adding ruby gems to the path + if ($Environment.IsDebianFamily) { + # These tools are not added to the path automatically on OpenSUSE 13.2 + # try adding them to the path and re-tesing first + [string] $gemsPath = $null + [string] $depenencyPath = $null + $gemsPath = Get-ChildItem -Path /usr/lib64/ruby/gems | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName + if ($gemsPath) { + $depenencyPath = Get-ChildItem -Path (Join-Path -Path $gemsPath -ChildPath "gems" -AdditionalChildPath $Dependency) -Recurse | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty DirectoryName + $originalPath = $env:PATH + $env:PATH = $ENV:PATH +":" + $depenencyPath + if ((precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { + continue + } + else { + $env:PATH = $originalPath + } } } @@ -2086,10 +2288,12 @@ function New-MacOSLauncher # Set permissions for plist and shell script. Start-NativeExecution { chmod 644 $plist + } + Start-NativeExecution { chmod 755 $shellscript } - # Add app folder to fpm paths. + # Return the app folder path for packaging $appsfolder = (Resolve-Path -Path "$macosapp/..").Path return $appsfolder diff --git a/tools/packaging/releaseTests/macOSPackage.tests.ps1 b/tools/packaging/releaseTests/macOSPackage.tests.ps1 new file mode 100644 index 00000000000..c1de1091562 --- /dev/null +++ b/tools/packaging/releaseTests/macOSPackage.tests.ps1 @@ -0,0 +1,162 @@ +Describe "Verify macOS Package" { + BeforeAll { + Write-Verbose "In Describe BeforeAll" -Verbose + Import-Module $PSScriptRoot/../../../build.psm1 + + # Find the macOS package + $packagePath = $env:PACKAGE_FOLDER + if (-not $packagePath) { + $packagePath = Get-Location + } + + Write-Verbose "Looking for package in: $packagePath" -Verbose + $package = Get-ChildItem -Path $packagePath -Filter "*.pkg" -ErrorAction SilentlyContinue | Select-Object -First 1 + + if (-not $package) { + Write-Warning "No .pkg file found in $packagePath" + } else { + Write-Verbose "Found package: $($package.FullName)" -Verbose + } + + # Set up test directories + $script:package = $package + $script:expandDir = $null + $script:payloadDir = $null + $script:extractedFiles = @() + + if ($package) { + # Use TestDrive for temporary directories - pkgutil will create the expand directory + $script:expandDir = Join-Path "TestDrive:" -ChildPath "package-contents-test" + $expandDirResolved = (Resolve-Path "TestDrive:").ProviderPath + $script:expandDir = Join-Path $expandDirResolved -ChildPath "package-contents-test" + + Write-Verbose "Expanding package to: $($script:expandDir)" -Verbose + # pkgutil will create the directory itself, so don't pre-create it + Start-NativeExecution { + pkgutil --expand $package.FullName $script:expandDir + } + + # Extract the payload to verify files + $script:payloadDir = Join-Path "TestDrive:" -ChildPath "package-payload-test" + $payloadDirResolved = (Resolve-Path "TestDrive:").ProviderPath + $script:payloadDir = Join-Path $payloadDirResolved -ChildPath "package-payload-test" + + # Create payload directory since cpio needs it + if (-not (Test-Path $script:payloadDir)) { + $null = New-Item -ItemType Directory -Path $script:payloadDir -Force + } + + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse | Select-Object -First 1 + if ($componentPkg) { + Write-Verbose "Extracting payload from: $($componentPkg.FullName)" -Verbose + Push-Location $script:payloadDir + try { + $payloadFile = Join-Path $componentPkg.FullName "Payload" + Get-Content -Path $payloadFile -Raw -AsByteStream | & cpio -i 2>&1 | Out-Null + } finally { + Pop-Location + } + } + + # Get all extracted files for verification + $script:extractedFiles = Get-ChildItem -Path $script:payloadDir -Recurse -ErrorAction SilentlyContinue + Write-Verbose "Extracted $($script:extractedFiles.Count) files" -Verbose + } + } + + AfterAll { + # TestDrive automatically cleans up, but we can ensure cleanup happens + # No manual cleanup needed as TestDrive handles it + } + + Context "Package existence and structure" { + It "Package file should exist" { + $script:package | Should -Not -BeNullOrEmpty -Because "A .pkg file should be created" + $script:package.Extension | Should -Be ".pkg" + } + + It "Package should expand successfully" { + $script:expandDir | Should -Exist + Get-ChildItem -Path $script:expandDir | Should -Not -BeNullOrEmpty + } + + It "Package should have a component package" { + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse -ErrorAction SilentlyContinue + $componentPkg | Should -Not -BeNullOrEmpty -Because "Package should contain a component.pkg" + } + + It "Payload should extract successfully" { + $script:payloadDir | Should -Exist + $script:extractedFiles | Should -Not -BeNullOrEmpty -Because "Package payload should contain files" + } + } + + Context "Required files in package" { + BeforeAll { + $expectedFilePatterns = @{ + "PowerShell executable" = "usr/local/microsoft/powershell/*/pwsh" + "PowerShell symlink in /usr/local/bin" = "usr/local/bin/pwsh*" + "Man page" = "usr/local/share/man/man1/pwsh*.gz" + "Launcher application plist" = "Applications/PowerShell*.app/Contents/Info.plist" + } + + $testCases = @() + foreach ($key in $expectedFilePatterns.Keys) { + $testCases += @{ + Description = $key + Pattern = $expectedFilePatterns[$key] + } + } + + $script:testCases = $testCases + } + + It "Should contain " -TestCases $script:testCases { + param($Description, $Pattern) + + $found = $script:extractedFiles | Where-Object { $_.FullName -like "*$Pattern*" } + $found | Should -Not -BeNullOrEmpty -Because "$Description should exist in the package at path matching '$Pattern'" + } + } + + Context "PowerShell binary verification" { + It "PowerShell executable should be executable" { + $pwshBinary = $script:extractedFiles | Where-Object { $_.FullName -like "*/pwsh" -and $_.FullName -like "*/microsoft/powershell/*" } + $pwshBinary | Should -Not -BeNullOrEmpty + + # Check if file has executable permissions (on Unix-like systems) + if ($IsLinux -or $IsMacOS) { + $permissions = (Get-Item $pwshBinary[0].FullName).UnixFileMode + # Executable bit should be set + $permissions.ToString() | Should -Match 'x' -Because "pwsh binary should have execute permissions" + } + } + } + + Context "Launcher application" { + It "Launcher app should have proper bundle structure" { + $plistFile = $script:extractedFiles | Where-Object { $_.FullName -like "*PowerShell*.app/Contents/Info.plist" } + $plistFile | Should -Not -BeNullOrEmpty + + # Verify the bundle has required components + $appPath = Split-Path (Split-Path $plistFile[0].FullName -Parent) -Parent + $macOSDir = Join-Path $appPath "Contents/MacOS" + $resourcesDir = Join-Path $appPath "Contents/Resources" + + Test-Path $macOSDir | Should -Be $true -Because "App bundle should have Contents/MacOS directory" + Test-Path $resourcesDir | Should -Be $true -Because "App bundle should have Contents/Resources directory" + } + + It "Launcher script should exist and be executable" { + $launcherScript = $script:extractedFiles | Where-Object { + $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" + } + $launcherScript | Should -Not -BeNullOrEmpty -Because "Launcher script should exist" + + if ($IsLinux -or $IsMacOS) { + $permissions = (Get-Item $launcherScript[0].FullName).UnixFileMode + $permissions.ToString() | Should -Match 'x' -Because "Launcher script should have execute permissions" + } + } + } +} From 7e1ef1f7448beabfc6292e1b97bc97512726438c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:39:28 -0700 Subject: [PATCH 080/255] Replace `fpm` with `dpkg-deb` for DEB package generation (#26281) --- build.psm1 | 76 +-- .../linux/package-validation.tests.ps1 | 48 +- tools/packaging/packaging.psm1 | 462 ++++++++++-------- 3 files changed, 315 insertions(+), 271 deletions(-) diff --git a/build.psm1 b/build.psm1 index 1b56d9d545d..e4e1a1f69f2 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2234,43 +2234,6 @@ function Get-RedHatPackageManager { } } -function Install-GlobalGem { - param( - [Parameter()] - [string] - $Sudo = "", - - [Parameter(Mandatory)] - [string] - $GemName, - - [Parameter(Mandatory)] - [string] - $GemVersion - ) - try { - # We cannot guess if the user wants to run gem install as root on linux and windows, - # but macOs usually requires sudo - $gemsudo = '' - if($environment.IsMacOS -or $env:TF_BUILD -or $env:GITHUB_ACTIONS) { - $gemsudo = $sudo - } - - Start-NativeExecution ([ScriptBlock]::Create("$gemsudo gem install $GemName -v $GemVersion --no-document")) - - } catch { - Write-Warning "Installation of gem $GemName $GemVersion failed! Must resolve manually." - $logs = Get-ChildItem "/var/lib/gems/*/extensions/x86_64-linux/*/$GemName-*/gem_make.out" | Select-Object -ExpandProperty FullName - foreach ($log in $logs) { - Write-Verbose "Contents of: $log" -Verbose - Get-Content -Raw -Path $log -ErrorAction Ignore | ForEach-Object { Write-Verbose $_ -Verbose } - Write-Verbose "END Contents of: $log" -Verbose - } - - throw - } -} - function Start-PSBootstrap { [CmdletBinding()] param( @@ -2282,7 +2245,7 @@ function Start-PSBootstrap { [switch]$BuildLinuxArm, [switch]$Force, [Parameter(Mandatory = $true)] - # Package: Install dependencies for packaging tools (fpm, rpmbuild, WiX) + # Package: Install dependencies for packaging tools (rpmbuild, dpkg-deb, pkgbuild, WiX) # DotNet: Install the .NET SDK # Both: Package and DotNet scenarios # Tools: Install .NET global tools (e.g., dotnet-format) @@ -2321,7 +2284,9 @@ function Start-PSBootstrap { elseif ($environment.IsUbuntu18) { $Deps += "libicu60"} # Packaging tools - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "ruby-dev", "groff", "libffi-dev", "rpm", "g++", "make" } + # Note: ruby-dev, libffi-dev, g++, and make are no longer needed for DEB packaging + # DEB packages now use native dpkg-deb (pre-installed) + if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "groff", "rpm" } # Install dependencies # change the fontend from apt-get to noninteractive @@ -2345,7 +2310,9 @@ function Start-PSBootstrap { $Deps += "libicu", "openssl-libs" # Packaging tools - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "ruby-devel", "rpm-build", "groff", 'libffi-devel', "gcc-c++" } + # Note: ruby-devel and libffi-devel are no longer needed + # RPM packages use rpmbuild, DEB packages use dpkg-deb + if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "rpm-build", "groff" } $PackageManager = Get-RedHatPackageManager @@ -2366,7 +2333,8 @@ function Start-PSBootstrap { $Deps += "wget" # Packaging tools - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "ruby-devel", "rpmbuild", "groff", 'libffi-devel', "gcc" } + # Note: ruby-devel and libffi-devel are no longer needed for packaging + if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "rpmbuild", "groff" } $PackageManager = "zypper --non-interactive install" $baseCommand = "$sudo $PackageManager" @@ -2405,17 +2373,7 @@ function Start-PSBootstrap { } } - # Install [fpm](https://github.com/jordansissel/fpm) - # Note: fpm is now only needed for DEB and macOS packages; RPM packages use rpmbuild directly if ($Scenario -in 'All', 'Both', 'Package') { - # Install fpm on Debian-based systems, macOS, and Mariner (where DEB packages are built) - if (($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) -or $environment.IsMacOS) { - Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1" - Install-GlobalGem -Sudo $sudo -GemName "ffi" -GemVersion "1.16.3" - Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1" - Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5" - } - # For RPM-based systems, ensure rpmbuild is available if ($environment.IsLinux -and ($environment.IsRedHatFamily -or $environment.IsSUSEFamily -or $environment.IsMariner)) { Write-Verbose -Verbose "Checking for rpmbuild..." @@ -2424,6 +2382,22 @@ function Start-PSBootstrap { Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y rpm-build")) -IgnoreExitcode } } + + # For Debian-based systems and Mariner, ensure dpkg-deb is available + if ($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) { + Write-Verbose -Verbose "Checking for dpkg-deb..." + if (!(Get-Command dpkg-deb -ErrorAction SilentlyContinue)) { + Write-Warning "dpkg-deb not found. Installing dpkg package..." + if ($environment.IsMariner) { + # For Mariner (Azure Linux), install the extended repo first to access dpkg. + Write-Verbose -Verbose "Installing azurelinux-repos-extended for Mariner..." + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y azurelinux-repos-extended")) -IgnoreExitcode + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y dpkg")) -IgnoreExitcode + } else { + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo apt-get install -y dpkg")) -IgnoreExitcode + } + } + } } } diff --git a/test/packaging/linux/package-validation.tests.ps1 b/test/packaging/linux/package-validation.tests.ps1 index 241863de45d..594a729fa77 100644 --- a/test/packaging/linux/package-validation.tests.ps1 +++ b/test/packaging/linux/package-validation.tests.ps1 @@ -21,10 +21,7 @@ Describe "Linux Package Name Validation" { It "Should have valid RPM package names" { $rpmPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.rpm -ErrorAction SilentlyContinue - if ($rpmPackages.Count -eq 0) { - Set-ItResult -Skipped -Because "No RPM packages found in artifacts directory" - return - } + $rpmPackages.Count | Should -BeGreaterThan 0 -Because "At least one RPM package should exist in the artifacts directory" $invalidPackages = @() # Regex pattern for valid RPM package names. @@ -49,8 +46,42 @@ Describe "Linux Package Name Validation" { if ($invalidPackages.Count -gt 0) { throw ($invalidPackages | Out-String) } + } + } + + Context "DEB Package Names" { + It "Should have valid DEB package names" { + $debPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.deb -ErrorAction SilentlyContinue + + $debPackages.Count | Should -BeGreaterThan 0 -Because "At least one DEB package should exist in the artifacts directory" + + $invalidPackages = @() + # Regex pattern for valid DEB package names. + # Valid examples: + # - powershell-preview_7.6.0-preview.6-1.deb_amd64.deb + # - powershell-lts_7.4.13-1.deb_amd64.deb + # - powershell_7.4.13-1.deb_amd64.deb + # Breakdown: + # ^powershell : Starts with 'powershell' + # (-preview|-lts)? : Optionally '-preview' or '-lts' + # _\d+\.\d+\.\d+ : Underscore followed by version number (e.g., _7.6.0) + # (-[a-z]+\.\d+)? : Optional dash, letters, dot, and digits (e.g., -preview.6) + # -1 : Literal '-1' + # \.deb_ : Literal '.deb_' + # (amd64|arm64) : Architecture + # \.deb$ : File extension + $debPackageNamePattern = '^powershell(-preview|-lts)?_\d+\.\d+\.\d+(-[a-z]+\.\d+)?-1\.deb_(amd64|arm64)\.deb$' + + foreach ($package in $debPackages) { + if ($package.Name -notmatch $debPackageNamePattern) { + $invalidPackages += "$($package.Name) is not a valid DEB package name" + Write-Warning "$($package.Name) is not a valid DEB package name" + } + } - $rpmPackages.Count | Should -BeGreaterThan 0 + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } } } @@ -58,10 +89,7 @@ Describe "Linux Package Name Validation" { It "Should have valid tar.gz package names" { $tarPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.tar.gz -ErrorAction SilentlyContinue - if ($tarPackages.Count -eq 0) { - Set-ItResult -Skipped -Because "No tar.gz packages found in artifacts directory" - return - } + $tarPackages.Count | Should -BeGreaterThan 0 -Because "At least one tar.gz package should exist in the artifacts directory" $invalidPackages = @() foreach ($package in $tarPackages) { @@ -76,8 +104,6 @@ Describe "Linux Package Name Validation" { if ($invalidPackages.Count -gt 0) { throw ($invalidPackages | Out-String) } - - $tarPackages.Count | Should -BeGreaterThan 0 } } diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 257b3694510..ccdc4e1cf08 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1199,8 +1199,6 @@ function New-UnixPackage { # Generate After Install and After Remove scripts $AfterScriptInfo = New-AfterScripts -Link $Link -Distribution $DebDistro -Destination $Destination - # Note: The fpm symlink workaround is no longer needed with native macOS packaging tools - # Generate gzip of man file $ManGzipInfo = New-ManGzip -IsPreview:$IsPreview -IsLTS:$LTS @@ -1319,6 +1317,33 @@ function New-UnixPackage { throw } } + } elseif ($Type -eq 'deb') { + # Use native DEB package builder + if ($PSCmdlet.ShouldProcess("Create DEB package natively")) { + Write-Log "Creating DEB package natively..." + try { + $result = New-NativeDeb ` + -Name $Name ` + -Version $packageVersion ` + -Iteration $Iteration ` + -Description $Description ` + -Staging $Staging ` + -Destination $Destination ` + -ManGzipFile $ManGzipInfo.GzipFile ` + -ManDestination $ManGzipInfo.ManFile ` + -LinkInfo $Links ` + -Dependencies $Dependencies ` + -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` + -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` + -HostArchitecture $HostArchitecture ` + -CurrentLocation $CurrentLocation + + $Output = @("Created package {:path=>""$($result.PackageName)""}") + } + catch { + Write-Verbose -Message "!!!Handling error in native DEB creation!!!" -Verbose -ErrorAction SilentlyContinue + } + } } elseif ($Type -eq 'osxpkg') { # Use native macOS packaging tools if ($PSCmdlet.ShouldProcess("Create macOS package with pkgbuild/productbuild")) { @@ -1350,40 +1375,8 @@ function New-UnixPackage { } } } else { - # Use fpm for DEB packages - $Arguments = @() - - $Arguments += Get-FpmArguments ` - -Name $Name ` - -Version $packageVersion ` - -Iteration $Iteration ` - -Description $Description ` - -Type $Type ` - -Dependencies $Dependencies ` - -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` - -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` - -Staging $Staging ` - -Destination $Destination ` - -ManGzipFile $ManGzipInfo.GzipFile ` - -ManDestination $ManGzipInfo.ManFile ` - -LinkInfo $Links ` - -AppsFolder $AppsFolder ` - -Distribution $DebDistro ` - -HostArchitecture $HostArchitecture ` - -ErrorAction Stop - - if ($PSCmdlet.ShouldProcess("Create $type package")) { - Write-Log "Creating package with fpm $Arguments..." - try { - $Output = Start-NativeExecution { fpm $Arguments } - } - catch { - Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue - Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue - Get-Error -InputObject $_ - throw - } - } + # Nothing should reach here + throw "Unknown package type: $Type" } } finally { if ($Environment.IsMacOS) { @@ -1416,8 +1409,7 @@ function New-UnixPackage { $createdPackage = Get-Item (Join-Path $CurrentLocation (($Output[-1] -split ":path=>")[-1] -replace '["{}]')) # For macOS with native tools, the package is already in the correct format - # For macOS with fpm (no longer used), we would need New-MacOsDistributionPackage - # For other platforms, the package name from fpm/rpmbuild is sufficient + # For other platforms, the package name from dpkg-deb/rpmbuild is sufficient if (Test-Path $createdPackage) { @@ -1695,8 +1687,8 @@ cp $ManGzipFile `$RPM_BUILD_ROOT$ManDestination foreach ($link in $LinkInfo) { $linkDir = Split-Path -Parent $link.Destination $specContent += "mkdir -p `$RPM_BUILD_ROOT$linkDir`n" - # For RPM, we copy the symlink itself (which fpm does by including it in the source) - # The symlink at $link.Source points to the actual target, so we'll copy it + # For RPM, we copy the symlink itself. + # The symlink at $link.Source points to the actual target, so we'll copy it. # The -P flag preserves symlinks rather than copying their targets, which is critical for this operation. $specContent += "cp -P $($link.Source) `$RPM_BUILD_ROOT$($link.Destination)`n" } @@ -1733,6 +1725,217 @@ cp $ManGzipFile `$RPM_BUILD_ROOT$ManDestination return $specContent } +function New-NativeDeb +{ + param( + [Parameter(Mandatory, HelpMessage='Package Name')] + [String]$Name, + + [Parameter(Mandatory, HelpMessage='Package Version')] + [String]$Version, + + [Parameter(Mandatory)] + [String]$Iteration, + + [Parameter(Mandatory, HelpMessage='Package description')] + [String]$Description, + + [Parameter(Mandatory, HelpMessage='Staging folder for installation files')] + [String]$Staging, + + [Parameter(Mandatory, HelpMessage='Install path on target machine')] + [String]$Destination, + + [Parameter(Mandatory, HelpMessage='The built and gzipped man file.')] + [String]$ManGzipFile, + + [Parameter(Mandatory, HelpMessage='The destination of the man file')] + [String]$ManDestination, + + [Parameter(Mandatory, HelpMessage='Symlink to powershell executable')] + [LinkInfo[]]$LinkInfo, + + [Parameter(HelpMessage='Packages required to install this package.')] + [String[]]$Dependencies, + + [Parameter(HelpMessage='Script to run after the package installation.')] + [String]$AfterInstallScript, + + [Parameter(HelpMessage='Script to run after the package removal.')] + [String]$AfterRemoveScript, + + [string]$HostArchitecture, + + [string]$CurrentLocation + ) + + Write-Log "Creating native DEB package..." + + # Create temporary build directory + $debBuildRoot = Join-Path $env:HOME "debbuild-$(Get-Random)" + $debianDir = Join-Path $debBuildRoot "DEBIAN" + $dataDir = Join-Path $debBuildRoot "data" + + try { + New-Item -ItemType Directory -Path $debianDir -Force | Out-Null + New-Item -ItemType Directory -Path $dataDir -Force | Out-Null + + # Calculate installed size (in KB) + $installedSize = 0 + Get-ChildItem -Path $Staging -Recurse -File | ForEach-Object { $installedSize += $_.Length } + $installedSize += (Get-Item $ManGzipFile).Length + $installedSizeKB = [Math]::Ceiling($installedSize / 1024) + + # Create control file with all fields in proper order + # Description must be single line (first line) followed by extended description with leading space + $descriptionLines = $Description -split "`n" + $shortDescription = $descriptionLines[0] + $extendedDescription = if ($descriptionLines.Count -gt 1) { + ($descriptionLines[1..($descriptionLines.Count-1)] | ForEach-Object { " $_" }) -join "`n" + } + + $controlContent = @" +Package: $Name +Version: $Version-$Iteration +Architecture: $HostArchitecture +Maintainer: PowerShell Team +Installed-Size: $installedSizeKB +Priority: optional +Section: shells +Homepage: https://microsoft.com/powershell +Depends: $(if ($Dependencies) { $Dependencies -join ', ' }) +Description: $shortDescription +$(if ($extendedDescription) { $extendedDescription + "`n" }) +"@ + + $controlFile = Join-Path $debianDir "control" + $controlContent | Out-File -FilePath $controlFile -Encoding ascii -NoNewline + + Write-Verbose "Control file created: $controlFile" -Verbose + Write-LogGroup -Title "DEB Control File Content" -Message $controlContent + + # Copy postinst script if provided + if ($AfterInstallScript -and (Test-Path $AfterInstallScript)) { + $postinstFile = Join-Path $debianDir "postinst" + Copy-Item -Path $AfterInstallScript -Destination $postinstFile -Force + Start-NativeExecution { chmod 755 $postinstFile } + Write-Verbose "Postinst script copied to: $postinstFile" -Verbose + } + + # Copy postrm script if provided + if ($AfterRemoveScript -and (Test-Path $AfterRemoveScript)) { + $postrmFile = Join-Path $debianDir "postrm" + Copy-Item -Path $AfterRemoveScript -Destination $postrmFile -Force + Start-NativeExecution { chmod 755 $postrmFile } + Write-Verbose "Postrm script copied to: $postrmFile" -Verbose + } + + # Copy staging files to data directory + $targetPath = Join-Path $dataDir $Destination.TrimStart('/') + New-Item -ItemType Directory -Path $targetPath -Force | Out-Null + Copy-Item -Path "$Staging/*" -Destination $targetPath -Recurse -Force + Write-Verbose "Copied staging files to: $targetPath" -Verbose + + # Copy man page + $manDestPath = Join-Path $dataDir $ManDestination.TrimStart('/') + $manDestDir = Split-Path $manDestPath -Parent + New-Item -ItemType Directory -Path $manDestDir -Force | Out-Null + Copy-Item -Path $ManGzipFile -Destination $manDestPath -Force + Write-Verbose "Copied man page to: $manDestPath" -Verbose + + # Copy symlinks from temporary locations + foreach ($link in $LinkInfo) { + $linkPath = Join-Path $dataDir $link.Destination.TrimStart('/') + $linkDir = Split-Path $linkPath -Parent + New-Item -ItemType Directory -Path $linkDir -Force | Out-Null + + # Copy the temporary symlink file that was created by New-LinkInfo + # The Source contains a temporary symlink that points to the correct target + if (Test-Path $link.Source) { + # Use cp to preserve the symlink + Start-NativeExecution { cp -P $link.Source $linkPath } + Write-Verbose "Copied symlink: $linkPath (from $($link.Source))" -Verbose + } else { + Write-Warning "Symlink source not found: $($link.Source)" + } + } + + # Set proper permissions + Write-Verbose "Setting file permissions..." -Verbose + # 755 = rwxr-xr-x (owner can read/write/execute, group and others can read/execute) + Get-ChildItem $dataDir -Directory -Recurse | ForEach-Object { + Start-NativeExecution { chmod 755 $_.FullName } + } + # 644 = rw-r--r-- (owner can read/write, group and others can read only) + # Exclude symlinks to avoid "cannot operate on dangling symlink" error + Get-ChildItem $dataDir -File -Recurse | + Where-Object { -not $_.Target } | + ForEach-Object { + Start-NativeExecution { chmod 644 $_.FullName } + } + + # Set executable permission for pwsh if it exists + # 755 = rwxr-xr-x (executable permission) + $pwshPath = "$targetPath/pwsh" + if (Test-Path $pwshPath) { + Start-NativeExecution { chmod 755 $pwshPath } + } + + # Calculate md5sums for all files in data directory (excluding symlinks) + $md5sumsFile = Join-Path $debianDir "md5sums" + $md5Content = "" + Get-ChildItem -Path $dataDir -Recurse -File | + Where-Object { -not $_.Target } | + ForEach-Object { + $relativePath = $_.FullName.Substring($dataDir.Length + 1) + $md5Hash = (Get-FileHash -Path $_.FullName -Algorithm MD5).Hash.ToLower() + $md5Content += "$md5Hash $relativePath`n" + } + $md5Content | Out-File -FilePath $md5sumsFile -Encoding ascii -NoNewline + Write-Verbose "MD5 sums file created: $md5sumsFile" -Verbose + + # Build the package using dpkg-deb + $debFileName = "${Name}_${Version}-${Iteration}_${HostArchitecture}.deb" + $debFilePath = Join-Path $CurrentLocation $debFileName + + Write-Verbose "Building DEB package: $debFileName" -Verbose + + # Copy DEBIAN directory and data files to build root + $buildDir = Join-Path $debBuildRoot "build" + New-Item -ItemType Directory -Path $buildDir -Force | Out-Null + + Write-Verbose "debianDir: $debianDir" -Verbose + Write-Verbose "dataDir: $dataDir" -Verbose + Write-Verbose "buildDir: $buildDir" -Verbose + + # Use cp to preserve symlinks + Start-NativeExecution { cp -a $debianDir "$buildDir/DEBIAN" } + Start-NativeExecution { cp -a $dataDir/* $buildDir } + + # Build package with dpkg-deb + Start-NativeExecution -VerboseOutputOnError { + dpkg-deb --build $buildDir $debFilePath + } + + if (Test-Path $debFilePath) { + Write-Log "Successfully created DEB package: $debFileName" + return @{ + PackagePath = $debFilePath + PackageName = $debFileName + } + } else { + throw "DEB package file not found after build: $debFilePath" + } + } + finally { + # Cleanup temporary directory + if (Test-Path $debBuildRoot) { + Write-Verbose "Cleaning up temporary build directory: $debBuildRoot" -Verbose + Remove-Item -Path $debBuildRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + function New-MacOSPackage { [CmdletBinding(SupportsShouldProcess=$true)] @@ -1884,146 +2087,6 @@ function New-MacOSPackage } } -function Get-FpmArguments -{ - param( - [Parameter(Mandatory,HelpMessage='Package Name')] - [String]$Name, - - [Parameter(Mandatory,HelpMessage='Package Version')] - [String]$Version, - - [Parameter(Mandatory)] - [String]$Iteration, - - [Parameter(Mandatory,HelpMessage='Package description')] - [String]$Description, - - # From start-PSPackage without modification, already validated - # Values: deb, rpm, osxpkg - [Parameter(Mandatory,HelpMessage='Installer Type')] - [String]$Type, - - [Parameter(Mandatory,HelpMessage='Staging folder for installation files')] - [String]$Staging, - - [Parameter(Mandatory,HelpMessage='Install path on target machine')] - [String]$Destination, - - [Parameter(Mandatory,HelpMessage='The built and gzipped man file.')] - [String]$ManGzipFile, - - [Parameter(Mandatory,HelpMessage='The destination of the man file')] - [String]$ManDestination, - - [Parameter(Mandatory,HelpMessage='Symlink to powershell executable')] - [LinkInfo[]]$LinkInfo, - - [Parameter(HelpMessage='Packages required to install this package. Not applicable for MacOS.')] - [ValidateScript({ - if (!$Environment.IsMacOS -and $_.Count -eq 0) - { - throw "Must not be null or empty on this environment." - } - return $true - })] - [String[]]$Dependencies, - - [Parameter(HelpMessage='Script to run after the package installation.')] - [AllowNull()] - [ValidateScript({ - if (!$Environment.IsMacOS -and !$_) - { - throw "Must not be null on this environment." - } - return $true - })] - [String]$AfterInstallScript, - - [Parameter(HelpMessage='Script to run after the package removal.')] - [AllowNull()] - [ValidateScript({ - if (!$Environment.IsMacOS -and !$_) - { - throw "Must not be null on this environment." - } - return $true - })] - [String]$AfterRemoveScript, - - [Parameter(HelpMessage='AppsFolder used to add macOS launcher')] - [AllowNull()] - [ValidateScript({ - if ($Environment.IsMacOS -and !$_) - { - throw "Must not be null on this environment." - } - return $true - })] - [String]$AppsFolder, - [String]$Distribution = 'rhel.7', - [string]$HostArchitecture - ) - - $Arguments = @( - "--force", "--verbose", - "--name", $Name, - "--version", $Version, - "--iteration", $Iteration, - "--maintainer", "PowerShell Team ", - "--vendor", "Microsoft Corporation", - "--url", "https://microsoft.com/powershell", - "--description", $Description, - "--architecture", $HostArchitecture, - "--category", "shells", - "-t", $Type, - "-s", "dir" - ) - if ($Distribution -in $script:RedHatDistributions) { - $Arguments += @("--rpm-digest", "sha256") - $Arguments += @("--rpm-dist", $Distribution) - $Arguments += @("--rpm-os", "linux") - $Arguments += @("--license", "MIT") - $Arguments += @("--rpm-rpmbuild-define", "_build_id_links none") - } else { - $Arguments += @("--license", "MIT License") - } - - if ($Environment.IsMacOS) { - $Arguments += @("--osxpkg-identifier-prefix", "com.microsoft") - } - - foreach ($Dependency in $Dependencies) { - $Arguments += @("--depends", $Dependency) - } - - if ($AfterInstallScript) { - $Arguments += @("--after-install", $AfterInstallScript) - } - - if ($AfterRemoveScript) { - $Arguments += @("--after-remove", $AfterRemoveScript) - } - - $Arguments += @( - "$Staging/=$Destination/", - "$ManGzipFile=$ManDestination" - ) - - foreach($link in $LinkInfo) - { - $linkArgument = "$($link.Source)=$($link.Destination)" - $Arguments += $linkArgument - } - - if ($AppsFolder) - { - $Arguments += "$AppsFolder=/" - } - - return $Arguments -} - function Get-PackageDependencies { [CmdletBinding()] @@ -2096,44 +2159,25 @@ function Get-PackageDependencies function Test-Dependencies { - # Note: RPM packages no longer require fpm; they use rpmbuild directly - # macOS packages use pkgbuild and productbuild from Xcode Command Line Tools - # DEB packages still use fpm + # RPM packages use rpmbuild directly. + # DEB packages use dpkg-deb directly. + # macOS packages use pkgbuild and productbuild from Xcode Command Line Tools. $Dependencies = @() - - # Only check for fpm on Debian-based systems - if ($Environment.IsDebianFamily) { - $Dependencies += "fpm" + + # Check for 'rpmbuild' and 'dpkg-deb' on Azure Linux. + if ($Environment.IsMariner) { + $Dependencies += "dpkg-deb" + $Dependencies += "rpmbuild" } - + # Check for macOS packaging tools if ($Environment.IsMacOS) { $Dependencies += "pkgbuild" $Dependencies += "productbuild" } - + foreach ($Dependency in $Dependencies) { if (!(precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { - # For Debian systems, try adding ruby gems to the path - if ($Environment.IsDebianFamily) { - # These tools are not added to the path automatically on OpenSUSE 13.2 - # try adding them to the path and re-tesing first - [string] $gemsPath = $null - [string] $depenencyPath = $null - $gemsPath = Get-ChildItem -Path /usr/lib64/ruby/gems | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName - if ($gemsPath) { - $depenencyPath = Get-ChildItem -Path (Join-Path -Path $gemsPath -ChildPath "gems" -AdditionalChildPath $Dependency) -Recurse | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty DirectoryName - $originalPath = $env:PATH - $env:PATH = $ENV:PATH +":" + $depenencyPath - if ((precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { - continue - } - else { - $env:PATH = $originalPath - } - } - } - throw "Dependency precheck failed!" } } From df8fe254b9ee0c04d24fe5ac010fdda7b26b9c71 Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:49:53 -0700 Subject: [PATCH 081/255] Update SDK to 10.0.100-rc.2.25502.107 (#26305) Co-authored-by: Dongbo Wang Co-authored-by: Travis Plunk --- .github/workflows/linux-ci.yml | 25 ++-- global.json | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 5 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 10 +- .../Microsoft.WSMan.Management.csproj | 2 +- .../System.Management.Automation.csproj | 12 +- .../nested/Test.Isolated.Nested.csproj | 2 +- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- test/xUnit/xUnit.tests.csproj | 2 +- tools/cgmanifest.json | 114 ++++++++++-------- 14 files changed, 97 insertions(+), 87 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 1650638be8e..9c0652a9359 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -157,17 +157,18 @@ jobs: runner_os: ubuntu-latest test_results_artifact_name: testResults-xunit - analyze: - name: CodeQL Analysis - needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} - uses: ./.github/workflows/analyze-reusable.yml - permissions: - actions: read - contents: read - security-events: write - with: - runner_os: ubuntu-latest + ## Temporarily disable the CodeQL analysis on Linux as it doesn't work for .NET SDK 10-rc.2. + # analyze: + # name: CodeQL Analysis + # needs: changes + # if: ${{ needs.changes.outputs.source == 'true' }} + # uses: ./.github/workflows/analyze-reusable.yml + # permissions: + # actions: read + # contents: read + # security-events: write + # with: + # runner_os: ubuntu-latest ready_to_merge: name: Linux ready to merge @@ -177,8 +178,8 @@ jobs: - linux_test_elevated_others - linux_test_unelevated_ci - linux_test_unelevated_others - - analyze - linux_packaging + # - analyze if: always() uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 with: diff --git a/global.json b/global.json index e7cce33e5d9..83e6a8b43e0 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.100-rc.1.25451.107" + "version": "10.0.100-rc.2.25502.107" } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index 547911668e9..cc3760db39f 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index 029571c4145..f5f6fede1af 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index a35f4258e56..da15fe82350 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -8,7 +8,7 @@ - + @@ -33,8 +33,7 @@ - - + diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index d60337d5d90..228d6360022 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 2c796312a1a..31c48def673 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,13 +16,13 @@ - - + + - - + + - + diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 710102ca2fa..bef80519d56 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 033c39034ac..0e435448b5f 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,17 +32,17 @@ - - + + - + - + - - + + diff --git a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj index 10c707c176a..de06b206f37 100644 --- a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj +++ b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index f50e3bccf60..d2004ea2fdd 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index 46638197255..056f6810c71 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ - + diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 90ae1a9b453..5cb4a1e48d8 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -29,7 +29,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json index 11c7447ff7d..26936804614 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/component-detection-manifest.json", "Registrations": [ { "Component": { @@ -65,7 +66,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.42.0" + "Version": "0.43.0" } }, "DevelopmentDependency": false @@ -85,7 +86,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -125,7 +126,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -165,7 +166,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -175,7 +176,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -185,7 +186,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -205,7 +206,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -215,7 +216,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -225,7 +226,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -235,7 +236,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -245,7 +246,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -255,7 +256,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -265,7 +266,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -275,7 +276,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -285,7 +286,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -295,7 +296,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -305,7 +306,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -315,7 +316,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -325,7 +326,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -335,7 +336,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -355,7 +356,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -365,7 +366,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -375,7 +376,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -435,7 +436,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -445,7 +446,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -455,7 +456,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -465,7 +466,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -475,7 +476,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -485,7 +486,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -505,7 +506,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -515,7 +516,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -525,7 +526,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -535,7 +536,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -545,7 +546,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -555,7 +556,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -565,7 +566,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -575,7 +576,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -585,7 +586,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -595,7 +596,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -615,7 +616,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -625,7 +626,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -635,7 +636,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -645,7 +646,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -655,7 +656,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -665,7 +666,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -725,7 +726,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -735,7 +736,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -745,7 +746,17 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "9.0.9" + "Version": "9.0.10" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Text.Encoding.CodePages", + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -755,7 +766,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Threading.AccessControl", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false @@ -775,11 +786,10 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "9.0.9" + "Version": "9.0.10" } }, "DevelopmentDependency": false } - ], - "$schema": "https://json.schemastore.org/component-detection-manifest.json" + ] } From bf3d978b9df2c831413016a93711120acf3bb079 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:05:28 -0700 Subject: [PATCH 082/255] Bump actions/upload-artifact from 4 to 5 (#26328) --- .github/workflows/macos-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 95edb2b8d96..077218d66e7 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -215,7 +215,7 @@ jobs: testResultsFolder: "${{ runner.workspace }}/testResults" - name: Upload package artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: macos-package path: "*.pkg" From 0d7c24fb7e4580a8000a2b574b073349f9ce66d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:05:55 -0700 Subject: [PATCH 083/255] Bump actions/setup-dotnet from 4 to 5 (#26327) --- .github/workflows/macos-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 077218d66e7..f43fa6a4b89 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -164,7 +164,7 @@ jobs: uses: actions/checkout@v5 with: fetch-depth: 1000 - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: global-json-file: ./global.json - name: Bootstrap packaging From cdeda2a50140a965a31d2ff7a575524b6caa2814 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:52:59 -0700 Subject: [PATCH 084/255] Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26326) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/actions/build/ci/action.yml | 4 +- .../actions/test/linux-packaging/action.yml | 4 +- .github/actions/test/nix/action.yml | 6 +- .github/actions/test/windows/action.yml | 6 +- .../log-grouping-guidelines.instructions.md | 181 ++++++++++++++++++ .github/workflows/analyze-reusable.yml | 3 +- .github/workflows/linux-ci.yml | 13 +- .github/workflows/macos-ci.yml | 15 +- .github/workflows/windows-ci.yml | 15 +- .../workflows/windows-packaging-reusable.yml | 3 +- build.psm1 | 28 ++- tools/ci.psm1 | 8 + 12 files changed, 253 insertions(+), 33 deletions(-) create mode 100644 .github/instructions/log-grouping-guidelines.instructions.md diff --git a/.github/actions/build/ci/action.yml b/.github/actions/build/ci/action.yml index 997be8b264c..be9c0ecd20b 100644 --- a/.github/actions/build/ci/action.yml +++ b/.github/actions/build/ci/action.yml @@ -5,7 +5,9 @@ runs: steps: - name: Capture Environment if: success() || failure() - run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + run: |- + Import-Module .\tools\ci.psm1 + Show-Environment shell: pwsh - name: Set Build Name for Non-PR if: github.event_name != 'PullRequest' diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index 374205f2a56..d0c72c7b035 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -6,7 +6,9 @@ runs: steps: - name: Capture Environment if: success() || failure() - run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + run: |- + Import-Module ./tools/ci.psm1 + Show-Environment shell: pwsh - uses: actions/setup-dotnet@v5 diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml index b338c398340..ef943bfce78 100644 --- a/.github/actions/test/nix/action.yml +++ b/.github/actions/test/nix/action.yml @@ -21,10 +21,8 @@ runs: - name: Capture Environment if: success() || failure() run: |- - Import-Module ./build.psm1 - Write-LogGroupStart -Title 'Environment' - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - Write-LogGroupEnd -Title 'Environment' + Import-Module ./tools/ci.psm1 + Show-Environment shell: pwsh - name: Download Build Artifacts diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml index 734e30208f0..3b3ce0cafe8 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -21,10 +21,8 @@ runs: - name: Capture Environment if: success() || failure() run: |- - Import-Module ./build.psm1 - Write-LogGroupStart -Title 'Environment' - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - Write-LogGroupEnd -Title 'Environment' + Import-Module ./tools/ci.psm1 + Show-Environment shell: pwsh - name: Download Build Artifacts diff --git a/.github/instructions/log-grouping-guidelines.instructions.md b/.github/instructions/log-grouping-guidelines.instructions.md new file mode 100644 index 00000000000..ff845db4e4b --- /dev/null +++ b/.github/instructions/log-grouping-guidelines.instructions.md @@ -0,0 +1,181 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Log Grouping Guidelines for GitHub Actions + +## Purpose + +Guidelines for using `Write-LogGroupStart` and `Write-LogGroupEnd` to create collapsible log sections in GitHub Actions CI/CD runs. + +## Key Principles + +### 1. Groups Cannot Be Nested + +GitHub Actions does not support nested groups. Only use one level of grouping. + +**❌ Don't:** +```powershell +Write-LogGroupStart -Title "Outer Group" +Write-LogGroupStart -Title "Inner Group" +# ... operations ... +Write-LogGroupEnd -Title "Inner Group" +Write-LogGroupEnd -Title "Outer Group" +``` + +**✅ Do:** +```powershell +Write-LogGroupStart -Title "Operation A" +# ... operations ... +Write-LogGroupEnd -Title "Operation A" + +Write-LogGroupStart -Title "Operation B" +# ... operations ... +Write-LogGroupEnd -Title "Operation B" +``` + +### 2. Groups Should Be Substantial + +Only create groups for operations that generate substantial output (5+ lines). Small groups add clutter without benefit. + +**❌ Don't:** +```powershell +Write-LogGroupStart -Title "Generate Resource Files" +Write-Log -message "Run ResGen" +Start-ResGen +Write-LogGroupEnd -Title "Generate Resource Files" +``` + +**✅ Do:** +```powershell +Write-Log -message "Run ResGen (generating C# bindings for resx files)" +Start-ResGen +``` + +### 3. Groups Should Represent Independent Operations + +Each group should be a logically independent operation that users might want to expand/collapse separately. + +**✅ Good examples:** +- Install Native Dependencies +- Install .NET SDK +- Build PowerShell +- Restore NuGet Packages + +**❌ Bad examples:** +- Individual project restores (too granular) +- Small code generation steps (too small) +- Sub-steps of a larger operation (would require nesting) + +### 4. One Group Per Iteration Is Excessive + +Avoid putting log groups inside loops where each iteration creates a separate group. This would probably cause nesting. + +**❌ Don't:** +```powershell +$projects | ForEach-Object { + Write-LogGroupStart -Title "Restore Project: $_" + dotnet restore $_ + Write-LogGroupEnd -Title "Restore Project: $_" +} +``` + +**✅ Do:** +```powershell +Write-LogGroupStart -Title "Restore All Projects" +$projects | ForEach-Object { + Write-Log -message "Restoring $_" + dotnet restore $_ +} +Write-LogGroupEnd -Title "Restore All Projects" +``` + +## Usage Pattern + +```powershell +Write-LogGroupStart -Title "Descriptive Operation Name" +try { + # ... operation code ... + Write-Log -message "Status updates" +} +finally { + # Ensure group is always closed +} +Write-LogGroupEnd -Title "Descriptive Operation Name" +``` + +## When to Use Log Groups + +Use log groups for: +- Major build phases (bootstrap, restore, build, test, package) +- Installation operations (dependencies, SDKs, tools) +- Operations that produce 5+ lines of output +- Operations where users might want to collapse verbose output + +Don't use log groups for: +- Single-line operations +- Code that's already inside another group +- Loop iterations with minimal output per iteration +- Diagnostic or debug output that should always be visible + +## Examples from build.psm1 + +### Good Usage + +```powershell +function Start-PSBootstrap { + # Multiple independent operations, each with substantial output + Write-LogGroupStart -Title "Install Native Dependencies" + # ... apt-get/yum/brew install commands ... + Write-LogGroupEnd -Title "Install Native Dependencies" + + Write-LogGroupStart -Title "Install .NET SDK" + # ... dotnet installation ... + Write-LogGroupEnd -Title "Install .NET SDK" +} +``` + +### Avoid + +```powershell +# Too small - just 2-3 lines +Write-LogGroupStart -Title "Generate Resource Files (ResGen)" +Write-Log -message "Run ResGen" +Start-ResGen +Write-LogGroupEnd -Title "Generate Resource Files (ResGen)" +``` + +## GitHub Actions Syntax + +These functions emit GitHub Actions workflow commands: +- `Write-LogGroupStart` → `::group::Title` +- `Write-LogGroupEnd` → `::endgroup::` + +In the GitHub Actions UI, this renders as collapsible sections with the specified title. + +## Testing + +Test log grouping locally: +```powershell +$env:GITHUB_ACTIONS = 'true' +Import-Module ./build.psm1 +Write-LogGroupStart -Title "Test" +Write-Log -Message "Content" +Write-LogGroupEnd -Title "Test" +``` + +Output should show: +``` +::group::Test +Content +::endgroup:: +``` + +## References + +- [GitHub Actions: Grouping log lines](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines) +- `build.psm1`: `Write-LogGroupStart` and `Write-LogGroupEnd` function definitions diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 1797e2234a6..10b2f0893a3 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -56,7 +56,8 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - run: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + Import-Module .\tools\ci.psm1 + Show-Environment name: Capture Environment shell: pwsh diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 9c0652a9359..8dc0e738ffd 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -53,6 +53,7 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout @@ -70,7 +71,7 @@ jobs: name: Build PowerShell runs-on: ubuntu-latest needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout uses: actions/checkout@v5 @@ -84,7 +85,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout @@ -101,7 +102,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout @@ -118,7 +119,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout @@ -135,7 +136,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout @@ -151,7 +152,7 @@ jobs: name: xUnit Tests needs: - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} uses: ./.github/workflows/xunit-tests.yml with: runner_os: ubuntu-latest diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index f43fa6a4b89..8a80b79f1c0 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -53,6 +53,7 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} steps: - name: checkout uses: actions/checkout@v5 @@ -67,7 +68,7 @@ jobs: name: Build PowerShell runs-on: macos-15-large needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout uses: actions/checkout@v5 @@ -80,7 +81,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: macos-15-large steps: - name: checkout @@ -97,7 +98,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: macos-15-large steps: - name: checkout @@ -114,7 +115,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: macos-15-large steps: - name: checkout @@ -131,7 +132,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: macos-15-large steps: - name: checkout @@ -147,7 +148,7 @@ jobs: name: xUnit Tests needs: - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} uses: ./.github/workflows/xunit-tests.yml with: runner_os: macos-15-large @@ -156,7 +157,7 @@ jobs: name: macOS packaging and testing needs: - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: - macos-15-large steps: diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index f33f1a3f589..582860de34c 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -56,6 +56,7 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout @@ -70,7 +71,7 @@ jobs: ci_build: name: Build PowerShell needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout @@ -84,7 +85,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout @@ -101,7 +102,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout @@ -118,7 +119,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout @@ -135,7 +136,7 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout @@ -151,7 +152,7 @@ jobs: name: xUnit Tests needs: - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} uses: ./.github/workflows/xunit-tests.yml with: runner_os: windows-latest @@ -159,7 +160,7 @@ jobs: analyze: name: CodeQL Analysis needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} uses: ./.github/workflows/analyze-reusable.yml permissions: actions: read diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index 0afa912273c..1f03aaf5944 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -43,7 +43,8 @@ jobs: - name: Capture Environment if: success() || failure() run: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + Import-Module .\tools\ci.psm1 + Show-Environment shell: pwsh - name: Capture PowerShell Version Table diff --git a/build.psm1 b/build.psm1 index e4e1a1f69f2..c56303524be 100644 --- a/build.psm1 +++ b/build.psm1 @@ -385,7 +385,7 @@ function Start-PSBuild { } if ($Clean) { - Write-Log -message "Cleaning your working directory. You can also do it with 'git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3'" + Write-LogGroupStart -Title "Cleaning your working directory" Push-Location $PSScriptRoot try { # Excluded sqlite3 folder is due to this Roslyn issue: https://github.com/dotnet/roslyn/issues/23060 @@ -393,6 +393,7 @@ function Start-PSBuild { # Excluded nuget.config as this is required for release build. git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3 --exclude src/Modules/nuget.config --exclude nuget.config } finally { + Write-LogGroupEnd -Title "Cleaning your working directory" Pop-Location } } @@ -536,7 +537,9 @@ Fix steps: } # handle Restore + Write-LogGroupStart -Title "Restore NuGet Packages" Restore-PSPackage -Options $Options -Force:$Restore -InteractiveAuth:$InteractiveAuth + Write-LogGroupEnd -Title "Restore NuGet Packages" # handle ResGen # Heuristic to run ResGen on the fresh machine @@ -566,6 +569,7 @@ Fix steps: $publishPath = $Options.Output } + Write-LogGroupStart -Title "Build PowerShell" try { # Relative paths do not work well if cwd is not changed to project Push-Location $Options.Top @@ -620,6 +624,7 @@ Fix steps: } finally { Pop-Location } + Write-LogGroupEnd -Title "Build PowerShell" # No extra post-building task will run if '-SMAOnly' is specified, because its purpose is for a quick update of S.M.A.dll after full build. if ($SMAOnly) { @@ -627,6 +632,7 @@ Fix steps: } # publish reference assemblies + Write-LogGroupStart -Title "Publish Reference Assemblies" try { Push-Location "$PSScriptRoot/src/TypeCatalogGen" $refAssemblies = Get-Content -Path $incFileName | Where-Object { $_ -like "*microsoft.netcore.app*" } | ForEach-Object { $_.TrimEnd(';') } @@ -640,6 +646,7 @@ Fix steps: } finally { Pop-Location } + Write-LogGroupEnd -Title "Publish Reference Assemblies" if ($ReleaseTag) { $psVersion = $ReleaseTag @@ -682,10 +689,13 @@ Fix steps: # download modules from powershell gallery. # - PowerShellGet, PackageManagement, Microsoft.PowerShell.Archive if ($PSModuleRestore) { + Write-LogGroupStart -Title "Restore PowerShell Modules" Restore-PSModuleToBuild -PublishPath $publishPath + Write-LogGroupEnd -Title "Restore PowerShell Modules" } # publish powershell.config.json + Write-LogGroupStart -Title "Generate PowerShell Configuration" $config = [ordered]@{} if ($Options.Runtime -like "*win*") { @@ -731,10 +741,13 @@ Fix steps: } else { Write-Warning "No powershell.config.json generated for $publishPath" } + Write-LogGroupEnd -Title "Generate PowerShell Configuration" # Restore the Pester module if ($CI) { + Write-LogGroupStart -Title "Restore Pester Module" Restore-PSPester -Destination (Join-Path $publishPath "Modules") + Write-LogGroupEnd -Title "Restore Pester Module" } Clear-NativeDependencies -PublishFolder $publishPath @@ -2086,6 +2099,7 @@ function Install-Dotnet { [string]$FeedCredential ) + Write-LogGroupStart -Title "Install .NET SDK $Version" Write-Verbose -Verbose "In install-dotnet" # This allows sudo install to be optional; needed when running in containers / as root @@ -2220,6 +2234,7 @@ function Install-Dotnet { } } } + Write-LogGroupEnd -Title "Install .NET SDK $Version" } function Get-RedHatPackageManager { @@ -2264,12 +2279,14 @@ function Start-PSBootstrap { try { if ($environment.IsLinux -or $environment.IsMacOS) { + Write-LogGroupStart -Title "Install Native Dependencies" # This allows sudo install to be optional; needed when running in containers / as root # Note that when it is null, Invoke-Expression (but not &) must be used to interpolate properly $sudo = if (!$NoSudo) { "sudo" } if ($BuildLinuxArm -and $environment.IsLinux -and -not $environment.IsUbuntu -and -not $environment.IsMariner) { Write-Error "Cross compiling for linux-arm is only supported on AzureLinux/Ubuntu environment" + Write-LogGroupEnd -Title "Install Native Dependencies" return } @@ -2399,9 +2416,11 @@ function Start-PSBootstrap { } } } + Write-LogGroupEnd -Title "Install Native Dependencies" } if ($Scenario -in 'All', 'Both', 'DotNet') { + Write-LogGroupStart -Title "Install .NET SDK" Write-Verbose -Verbose "Calling Find-Dotnet from Start-PSBootstrap" @@ -2440,10 +2459,12 @@ function Start-PSBootstrap { else { Write-Log -message "dotnet is already installed. Skipping installation." } + Write-LogGroupEnd -Title "Install .NET SDK" } # Install Windows dependencies if `-Package` or `-BuildWindowsNative` is specified if ($environment.IsWindows) { + Write-LogGroupStart -Title "Install Windows Dependencies" ## The VSCode build task requires 'pwsh.exe' to be found in Path if (-not (Get-Command -Name pwsh.exe -CommandType Application -ErrorAction Ignore)) { @@ -2456,9 +2477,11 @@ function Start-PSBootstrap { $isArm64 = "$env:RUNTIME" -eq 'arm64' Install-Wix -arm64:$isArm64 } + Write-LogGroupEnd -Title "Install Windows Dependencies" } if ($Scenario -in 'All', 'Tools') { + Write-LogGroupStart -Title "Install .NET Global Tools" Write-Log -message "Installing .NET global tools" # Ensure dotnet is available @@ -2469,12 +2492,15 @@ function Start-PSBootstrap { Start-NativeExecution { dotnet tool install --global dotnet-format } + Write-LogGroupEnd -Title "Install .NET Global Tools" } if ($env:TF_BUILD) { + Write-LogGroupStart -Title "Capture NuGet Sources" Write-Verbose -Verbose "--- Start - Capturing nuget sources" dotnet nuget list source --format detailed Write-Verbose -Verbose "--- End - Capturing nuget sources" + Write-LogGroupEnd -Title "Capture NuGet Sources" } } finally { Pop-Location diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 44651c26109..478435e8543 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -688,6 +688,14 @@ function Set-Path } } +# Display environment variables in a log group for GitHub Actions +function Show-Environment +{ + Write-LogGroupStart -Title 'Environment' + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | Write-Verbose -Verbose + Write-LogGroupEnd -Title 'Environment' +} + # Bootstrap script for Linux and macOS function Invoke-BootstrapStage { From d38d6fadf9064b508dc47a49e1df7cc923d10967 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 30 Oct 2025 10:28:52 -0700 Subject: [PATCH 085/255] Update the `Microsoft.PowerShell.Native` package version (#26347) --- .../System.Management.Automation.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 0e435448b5f..f3d73f72dd4 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -47,7 +47,7 @@ - + From 943257b247ba16dc67b1f7c126248bbc00ae1a21 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 30 Oct 2025 14:26:03 -0700 Subject: [PATCH 086/255] Make some experimental features stable (#26348) The following experimental features are made stable: - PSNativeWindowsTildeExpansion - PSRedirectToVariable - PSSubsystemPluginModel --- .../commands/utility/Var.cs | 1 - .../ExperimentalFeature.cs | 11 ---- .../engine/InitialSessionState.cs | 6 +-- .../engine/NativeCommandParameterBinder.cs | 7 +-- .../Commands/GetPSSubsystemCommand.cs | 1 - .../engine/runtime/Operations/MiscOps.cs | 17 +++---- .../Parser/RedirectionOperator.Tests.ps1 | 8 +-- .../NativeWindowsTildeExpansion.Tests.ps1 | 16 +++--- .../Set-Variable.Tests.ps1 | 50 +++++++------------ .../engine/Basic/DefaultCommands.Tests.ps1 | 1 + test/tools/TestMetadata.json | 3 -- 11 files changed, 37 insertions(+), 84 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index 12cfb784c72..a41b284f568 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -697,7 +697,6 @@ public SwitchParameter PassThru /// Gets whether we will append to the variable if it exists. /// [Parameter] - [Experimental(ExperimentalFeature.PSRedirectToVariable, ExperimentAction.Show)] public SwitchParameter Append { get; set; } private bool _nameIsFormalParameter; diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 6ff7e1d6820..4ac98cb485e 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -22,8 +22,6 @@ public class ExperimentalFeature internal const string EngineSource = "PSEngine"; internal const string PSFeedbackProvider = "PSFeedbackProvider"; - internal const string PSNativeWindowsTildeExpansion = nameof(PSNativeWindowsTildeExpansion); - internal const string PSRedirectToVariable = "PSRedirectToVariable"; internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); #endregion @@ -107,21 +105,12 @@ static ExperimentalFeature() name: "PSFileSystemProviderV2", description: "Replace the old FileSystemProvider with cleaner design and faster code"), */ - new ExperimentalFeature( - name: "PSSubsystemPluginModel", - description: "A plugin model for registering and un-registering PowerShell subsystems"), new ExperimentalFeature( name: "PSLoadAssemblyFromNativeCode", description: "Expose an API to allow assembly loading from native code"), new ExperimentalFeature( name: PSFeedbackProvider, description: "Replace the hard-coded suggestion framework with the extensible feedback provider"), - new ExperimentalFeature( - name: PSNativeWindowsTildeExpansion, - description: "On Windows, expand unquoted tilde (`~`) with the user's current home folder."), - new ExperimentalFeature( - name: PSRedirectToVariable, - description: "Add support for redirecting to the variable drive"), new ExperimentalFeature( name: PSSerializeJSONLongEnumAsNumber, description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index ba508560ee7..62308282d17 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -5478,6 +5478,7 @@ private static void InitializeCoreCmdletsAndProviders( { "Get-Module", new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), helpFile) }, { "Get-PSHostProcessInfo", new SessionStateCmdletEntry("Get-PSHostProcessInfo", typeof(GetPSHostProcessInfoCommand), helpFile) }, { "Get-PSSession", new SessionStateCmdletEntry("Get-PSSession", typeof(GetPSSessionCommand), helpFile) }, + { "Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile) }, { "Import-Module", new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), helpFile) }, { "Invoke-Command", new SessionStateCmdletEntry("Invoke-Command", typeof(InvokeCommandCommand), helpFile) }, { "Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) }, @@ -5518,11 +5519,6 @@ private static void InitializeCoreCmdletsAndProviders( { "Format-Default", new SessionStateCmdletEntry("Format-Default", typeof(FormatDefaultCommand), helpFile) }, }; - if (ExperimentalFeature.IsEnabled("PSSubsystemPluginModel")) - { - cmdlets.Add("Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile)); - } - #if UNIX cmdlets.Add("Switch-Process", new SessionStateCmdletEntry("Switch-Process", typeof(SwitchProcessCommand), helpFile)); #endif diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index 2e62274ee53..31ac506c86a 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -406,12 +406,9 @@ private void PossiblyGlobArg(string arg, CommandParameterInternal parameter, boo } } #else - if (!usedQuotes && ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeWindowsTildeExpansion)) + if (!usedQuotes && ExpandTilde(arg, parameter)) { - if (ExpandTilde(arg, parameter)) - { - argExpanded = true; - } + argExpanded = true; } #endif diff --git a/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs b/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs index 1ad79404f51..df828c724a2 100644 --- a/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs +++ b/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs @@ -8,7 +8,6 @@ namespace System.Management.Automation.Subsystem /// /// Implementation of 'Get-PSSubsystem' cmdlet. /// - [Experimental("PSSubsystemPluginModel", ExperimentAction.Show)] [Cmdlet(VerbsCommon.Get, "PSSubsystem", DefaultParameterSetName = AllSet)] [OutputType(typeof(SubsystemInfo))] public sealed class GetPSSubsystemCommand : PSCmdlet diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index ddc70fabd50..50054f6501d 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -1092,14 +1092,11 @@ internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcesso { // Check first to see if File is a variable path. If so, we'll not create the FileBytePipe bool redirectToVariable = false; - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSRedirectToVariable)) + + context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo p, out _); + if (p != null && p.NameEquals(context.ProviderNames.Variable)) { - ProviderInfo p; - context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out p, out _); - if (p != null && p.NameEquals(context.ProviderNames.Variable)) - { - redirectToVariable = true; - } + redirectToVariable = true; } if (commandProcessor is NativeCommandProcessor nativeCommand @@ -1223,12 +1220,10 @@ internal Pipe GetRedirectionPipe(ExecutionContext context, PipelineProcessor par // determine whether we're trying to set a variable by inspecting the file path // if we can determine that it's a variable, we'll use Set-Variable rather than Out-File - ProviderInfo p; - PSDriveInfo d; CommandProcessorBase commandProcessor; - var name = context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out p, out d); + var name = context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo p, out _); - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSRedirectToVariable) && p != null && p.NameEquals(context.ProviderNames.Variable)) + if (p != null && p.NameEquals(context.ProviderNames.Variable)) { commandProcessor = context.CreateCommand("Set-Variable", false); Diagnostics.Assert(commandProcessor != null, "CreateCommand returned null"); diff --git a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 index d948881f1de..140d92fae88 100644 --- a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 +++ b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 @@ -127,12 +127,6 @@ Describe "File redirection should have 'DoComplete' called on the underlying pip Describe "Redirection and Set-Variable -append tests" -tags CI { Context "variable redirection should work" { BeforeAll { - if ( $EnabledExperimentalFeatures -contains "PSRedirectToVariable" ) { - $skipTest = $false - } - else { - $skipTest = $true - } $testCases = @{ Name = "Variable should be created"; scriptBlock = { 1..3>variable:a }; Validation = { ($a -join "") | Should -Be ((1..3) -join "") } }, @{ Name = "variable should be appended"; scriptBlock = {1..3>variable:a; 4..6>>variable:a}; Validation = { ($a -join "") | Should -Be ((1..6) -join "")}}, @{ Name = "variable should maintain type"; scriptBlock = {@{one=1}>variable:a};Validation = {$a | Should -BeOfType [hashtable]}}, @@ -220,7 +214,7 @@ Describe "Redirection and Set-Variable -append tests" -tags CI { } - It "" -TestCases $testCases -skip:$skipTest { + It "" -TestCases $testCases { param ( $scriptBlock, $validation ) . $scriptBlock . $validation diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 index 04156099fa4..3a478607c08 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 @@ -5,7 +5,6 @@ Describe 'Native Windows tilde expansion tests' -tags "CI" { BeforeAll { $originalDefaultParams = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = -Not $IsWindows - $EnabledExperimentalFeatures.Contains('PSNativeWindowsTildeExpansion') | Should -BeTrue } AfterAll { @@ -21,12 +20,13 @@ Describe 'Native Windows tilde expansion tests' -tags "CI" { cmd /c echo ~/foo | Should -BeExactly "$($ExecutionContext.SessionState.Provider.Get("FileSystem").Home)/foo" cmd /c echo ~\foo | Should -BeExactly "$($ExecutionContext.SessionState.Provider.Get("FileSystem").Home)\foo" } - It '~ should not be replaced when quoted' { - cmd /c echo '~' | Should -BeExactly '~' - cmd /c echo "~" | Should -BeExactly '~' - cmd /c echo '~/foo' | Should -BeExactly '~/foo' - cmd /c echo "~/foo" | Should -BeExactly '~/foo' + + It '~ should not be replaced when quoted' { + cmd /c echo '~' | Should -BeExactly '~' + cmd /c echo "~" | Should -BeExactly '~' + cmd /c echo '~/foo' | Should -BeExactly '~/foo' + cmd /c echo "~/foo" | Should -BeExactly '~/foo' cmd /c echo '~\foo' | Should -BeExactly '~\foo' - cmd /c echo "~\foo" | Should -BeExactly '~\foo' - } + cmd /c echo "~\foo" | Should -BeExactly '~\foo' + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 index d11fd5cfbb3..6e6fc1a2e1d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 @@ -244,44 +244,30 @@ Describe "Set-Variable" -Tags "CI" { Context "Set-Variable -Append tests" { BeforeAll { - if (! (Get-ExperimentalFeature PSRedirectToVariable).Enabled) { - $skipTest = $true - } - - $testCases = @{ value = 2; Count = 2 }, - @{ value = @(2,3,4); Count = 2}, - @{ value = "abc",(Get-Process -Id $PID) ; count = 2} + $testCases = @{ value = 2; Count = 2 }, + @{ value = @(2,3,4); Count = 2}, + @{ value = "abc",(Get-Process -Id $PID) ; count = 2} } - It "Can append values to a variable" -testCases $testCases { - param ($value, $count) - - if ($skipTest) { - Set-ItResult -skip -because "Experimental Feature PSRedirectToVariable not enabled" - return - } - - $variableName = "testVar" - Set-Variable -Name $variableName -Value 1 - Set-Variable -Name $variableName -Value $value -Append + It "Can append values to a variable" -testCases $testCases { + param ($value, $count) - $observedValues = Get-Variable $variableName -Value + $variableName = "testVar" + Set-Variable -Name $variableName -Value 1 + Set-Variable -Name $variableName -Value $value -Append - $observedValues.Count | Should -Be $count - $observedValues[0] | Should -Be 1 + $observedValues = Get-Variable $variableName -Value - $observedValues[1] | Should -Be $value - } + $observedValues.Count | Should -Be $count + $observedValues[0] | Should -Be 1 - It "Can use set-variable via streaming and append values" { - if ($skipTest) { - Set-ItResult -skip -because "Experimental Feature PSRedirectToVariable not enabled" - return - } + $observedValues[1] | Should -Be $value + } - $testVar = 1 - 4..6 | Set-Variable -Name testVar -Append - $testVar | Should -Be @(1,4,5,6) - } + It "Can use set-variable via streaming and append values" { + $testVar = 1 + 4..6 | Set-Variable -Name testVar -Append + $testVar | Should -Be @(1,4,5,6) + } } } diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index 312c34b6706..7a757e6eac4 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -337,6 +337,7 @@ Describe "Verify aliases and cmdlets" -Tags "CI" { "Cmdlet", "Get-PSSessionCapability", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Get-PSSessionConfiguration", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Get-PSSnapin", "", $($FullCLR ), "", "", "" +"Cmdlet", "Get-PSSubsystem", "", $( $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Random", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Runspace", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-RunspaceDebug", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index bd716ccec7b..19ffb93ce92 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -1,9 +1,6 @@ { "ExperimentalFeatures": { "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ], - "PSCultureInvariantReplaceOperator": [ "test/powershell/Language/Operators/ReplaceOperator.Tests.ps1" ], - "Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1" ], - "PSNativeWindowsTildeExpansion": [ "test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1" ], "PSSerializeJSONLongEnumAsNumber": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1" ] } } From a9368846380d6b35706239e880cb088c64e36013 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Thu, 30 Oct 2025 14:27:19 -0700 Subject: [PATCH 087/255] Add reusable get-changed-files action and refactor existing actions (#26355) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../get-changed-files/README.md | 122 ++++++++++++++++++ .../get-changed-files/action.yml | 117 +++++++++++++++++ .../infrastructure/markdownlinks/action.yml | 47 ++----- .../infrastructure/path-filters/action.yml | 104 ++++++++------- 4 files changed, 304 insertions(+), 86 deletions(-) create mode 100644 .github/actions/infrastructure/get-changed-files/README.md create mode 100644 .github/actions/infrastructure/get-changed-files/action.yml diff --git a/.github/actions/infrastructure/get-changed-files/README.md b/.github/actions/infrastructure/get-changed-files/README.md new file mode 100644 index 00000000000..277b28c0674 --- /dev/null +++ b/.github/actions/infrastructure/get-changed-files/README.md @@ -0,0 +1,122 @@ +# Get Changed Files Action + +A reusable composite action that retrieves the list of files changed in a pull request or push event. + +## Features + +- Supports both `pull_request` and `push` events +- Optional filtering by file pattern +- Returns files as JSON array for easy consumption +- Filters out deleted files (only returns added, modified, or renamed files) +- Handles up to 100 changed files per request + +## Usage + +### Basic Usage (Pull Requests Only) + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + +- name: Process files + run: | + echo "Changed files: ${{ steps.changed-files.outputs.files }}" + echo "Count: ${{ steps.changed-files.outputs.count }}" +``` + +### With Filtering + +```yaml +# Get only markdown files +- name: Get changed markdown files + id: changed-md + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '*.md' + +# Get only GitHub workflow/action files +- name: Get changed GitHub files + id: changed-github + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '.github/' +``` + +### Support Both PR and Push Events + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + with: + event-types: 'pull_request,push' +``` + +## Inputs + +| Name | Description | Required | Default | +|------|-------------|----------|---------| +| `filter` | Optional filter pattern (e.g., `*.md` for markdown files, `.github/` for GitHub files) | No | `''` | +| `event-types` | Comma-separated list of event types to support (`pull_request`, `push`) | No | `pull_request` | + +## Outputs + +| Name | Description | +|------|-------------| +| `files` | JSON array of changed file paths | +| `count` | Number of changed files | + +## Filter Patterns + +The action supports simple filter patterns: + +- **Extension matching**: Use `*.ext` to match files with a specific extension + - Example: `*.md` matches all markdown files + - Example: `*.yml` matches all YAML files + +- **Path prefix matching**: Use a path prefix to match files in a directory + - Example: `.github/` matches all files in the `.github` directory + - Example: `tools/` matches all files in the `tools` directory + +## Example: Processing Changed Files + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + +- name: Process each file + shell: pwsh + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.files }} + run: | + $changedFilesJson = $env:CHANGED_FILES + $changedFiles = $changedFilesJson | ConvertFrom-Json + + foreach ($file in $changedFiles) { + Write-Host "Processing: $file" + # Your processing logic here + } +``` + +## Limitations + +- Simple filter patterns only (no complex glob or regex patterns) + +## Pagination + +The action automatically handles pagination to fetch **all** changed files in a PR, regardless of how many files were changed: + +- Fetches files in batches of 100 per page +- Continues fetching until all files are retrieved +- Logs a note when pagination occurs, showing the total file count +- **No file limit** - all changed files will be processed, even in very large PRs + +This ensures that critical workflows (such as merge conflict checking, link validation, etc.) don't miss files due to pagination limits. + +## Related Actions + +- **markdownlinks**: Uses this pattern to get changed markdown files +- **merge-conflict-checker**: Uses this pattern to get changed files for conflict detection +- **path-filters**: Similar functionality but with more complex filtering logic diff --git a/.github/actions/infrastructure/get-changed-files/action.yml b/.github/actions/infrastructure/get-changed-files/action.yml new file mode 100644 index 00000000000..c897d4f388d --- /dev/null +++ b/.github/actions/infrastructure/get-changed-files/action.yml @@ -0,0 +1,117 @@ +name: 'Get Changed Files' +description: 'Gets the list of files changed in a pull request or push event' +inputs: + filter: + description: 'Optional filter pattern (e.g., "*.md" for markdown files, ".github/" for GitHub files)' + required: false + default: '' + event-types: + description: 'Comma-separated list of event types to support (pull_request, push)' + required: false + default: 'pull_request' +outputs: + files: + description: 'JSON array of changed file paths' + value: ${{ steps.get-files.outputs.files }} + count: + description: 'Number of changed files' + value: ${{ steps.get-files.outputs.count }} +runs: + using: 'composite' + steps: + - name: Get changed files + id: get-files + uses: actions/github-script@v7 + with: + script: | + const eventTypes = '${{ inputs.event-types }}'.split(',').map(t => t.trim()); + const filter = '${{ inputs.filter }}'; + let changedFiles = []; + + if (eventTypes.includes('pull_request') && context.eventName === 'pull_request') { + console.log(`Getting files changed in PR #${context.payload.pull_request.number}`); + + // Fetch all files changed in the PR with pagination + let allFiles = []; + let page = 1; + let fetchedCount; + + do { + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100, + page: page + }); + + allFiles = allFiles.concat(files); + fetchedCount = files.length; + page++; + } while (fetchedCount === 100); + + if (allFiles.length >= 100) { + console.log(`Note: This PR has ${allFiles.length} changed files. All files fetched using pagination.`); + } + + changedFiles = allFiles + .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') + .map(file => file.filename); + + } else if (eventTypes.includes('push') && context.eventName === 'push') { + console.log(`Getting files changed in push to ${context.ref}`); + + const { data: comparison } = await github.rest.repos.compareCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + base: context.payload.before, + head: context.payload.after, + }); + + changedFiles = comparison.files + .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') + .map(file => file.filename); + + } else { + core.setFailed(`Unsupported event type: ${context.eventName}. Supported types: ${eventTypes.join(', ')}`); + return; + } + + // Apply filter if provided + if (filter) { + const filterLower = filter.toLowerCase(); + const beforeFilter = changedFiles.length; + changedFiles = changedFiles.filter(file => { + const fileLower = file.toLowerCase(); + // Support simple patterns like "*.md" or ".github/" + if (filterLower.startsWith('*.')) { + const ext = filterLower.substring(1); + return fileLower.endsWith(ext); + } else { + return fileLower.startsWith(filterLower); + } + }); + console.log(`Filter '${filter}' applied: ${beforeFilter} → ${changedFiles.length} files`); + } + + // Calculate simple hash for verification + const crypto = require('crypto'); + const filesJson = JSON.stringify(changedFiles.sort()); + const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); + + // Log changed files in a collapsible group + core.startGroup(`Changed Files (${changedFiles.length} total, hash: ${hash})`); + if (changedFiles.length > 0) { + changedFiles.forEach(file => console.log(` - ${file}`)); + } else { + console.log(' (no files changed)'); + } + core.endGroup(); + + console.log(`Found ${changedFiles.length} changed files`); + core.setOutput('files', JSON.stringify(changedFiles)); + core.setOutput('count', changedFiles.length); + +branding: + icon: 'file-text' + color: 'blue' diff --git a/.github/actions/infrastructure/markdownlinks/action.yml b/.github/actions/infrastructure/markdownlinks/action.yml index 1d6d0864784..de2952252d4 100644 --- a/.github/actions/infrastructure/markdownlinks/action.yml +++ b/.github/actions/infrastructure/markdownlinks/action.yml @@ -31,52 +31,23 @@ runs: steps: - name: Get changed markdown files id: changed-files - uses: actions/github-script@v7 + uses: "./.github/actions/infrastructure/get-changed-files" with: - script: | - let changedMarkdownFiles = []; - - if (context.eventName === 'pull_request') { - const { data: files } = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - }); - - changedMarkdownFiles = files - .filter(file => file.filename.endsWith('.md')) - .map(file => file.filename); - } else if (context.eventName === 'push') { - const { data: comparison } = await github.rest.repos.compareCommits({ - owner: context.repo.owner, - repo: context.repo.repo, - base: context.payload.before, - head: context.payload.after, - }); - - changedMarkdownFiles = comparison.files - .filter(file => file.filename.endsWith('.md')) - .map(file => file.filename); - } else { - core.setFailed(`Unsupported event type: ${context.eventName}. This action only supports 'pull_request' and 'push' events.`); - return; - } - - console.log('Changed markdown files:', changedMarkdownFiles); - core.setOutput('files', JSON.stringify(changedMarkdownFiles)); - core.setOutput('count', changedMarkdownFiles.length); - return changedMarkdownFiles; + filter: '*.md' + event-types: 'pull_request,push' - name: Verify markdown links id: verify shell: pwsh + env: + CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }} run: | Write-Host "Starting markdown link verification..." -ForegroundColor Cyan - # Get changed markdown files from previous step - $changedFilesJson = '${{ steps.changed-files.outputs.files }}' + # Get changed markdown files from environment variable (secure against injection) + $changedFilesJson = $env:CHANGED_FILES_JSON $changedFiles = $changedFilesJson | ConvertFrom-Json - + if ($changedFiles.Count -eq 0) { Write-Host "No markdown files changed, skipping verification" -ForegroundColor Yellow "total=0" >> $env:GITHUB_OUTPUT @@ -85,7 +56,7 @@ runs: "skipped=0" >> $env:GITHUB_OUTPUT exit 0 } - + Write-Host "Changed markdown files: $($changedFiles.Count)" -ForegroundColor Cyan $changedFiles | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray } diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml index ff07c4510fc..656719262b2 100644 --- a/.github/actions/infrastructure/path-filters/action.yml +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -32,9 +32,16 @@ outputs: runs: using: composite steps: + - name: Get changed files + id: get-files + if: github.event_name == 'pull_request' + uses: "./.github/actions/infrastructure/get-changed-files" + - name: Check if GitHubWorkflowChanges is present id: filter uses: actions/github-script@v7.0.1 + env: + FILES_JSON: ${{ steps.get-files.outputs.files }} with: github-token: ${{ inputs.GITHUB_TOKEN }} script: | @@ -53,57 +60,71 @@ runs: return; } - console.log(`Getting files changed in PR #${context.issue.number}`); - - // Fetch the list of files changed in the PR - let files = []; - let page = 1; - let fetchedFiles; - do { - fetchedFiles = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - per_page: 100, - page: page++ - }); - files = files.concat(fetchedFiles.data); - } while (fetchedFiles.data.length > 0); - - const actionsChanged = files.some(file => file.filename.startsWith('.github/actions')); - const workflowsChanged = files.some(file => file.filename.startsWith('.github/workflows')); + // Get files from environment variable (secure against injection) + const files = JSON.parse(process.env.FILES_JSON || '[]'); + + // Calculate hash for verification (matches get-changed-files action) + const crypto = require('crypto'); + const filesJson = JSON.stringify(files.sort()); + const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); + console.log(`Received ${files.length} files (hash: ${hash})`); + + // Analyze changes with detailed logging + core.startGroup('Path Filter Analysis'); + + const actionsChanged = files.some(file => file.startsWith('.github/actions')); + console.log(`✓ Actions changed: ${actionsChanged}`); + + const workflowsChanged = files.some(file => file.startsWith('.github/workflows')); + console.log(`✓ Workflows changed: ${workflowsChanged}`); + const githubChanged = actionsChanged || workflowsChanged; + console.log(`→ GitHub changed (actions OR workflows): ${githubChanged}`); + + const toolsCiPsm1Changed = files.some(file => file === 'tools/ci.psm1'); + console.log(`✓ tools/ci.psm1 changed: ${toolsCiPsm1Changed}`); + + const toolsBuildCommonChanged = files.some(file => file.startsWith('tools/buildCommon/')); + console.log(`✓ tools/buildCommon/ changed: ${toolsBuildCommonChanged}`); - const toolsCiPsm1Changed = files.some(file => file.filename.startsWith('tools/ci.psm1')); - const toolsBuildCommonChanged = files.some(file => file.filename.startsWith('tools/buildCommon/')); const toolsChanged = toolsCiPsm1Changed || toolsBuildCommonChanged; + console.log(`→ Tools changed: ${toolsChanged}`); - const propsChanged = files.some(file => file.filename.endsWith('.props')); + const propsChanged = files.some(file => file.endsWith('.props')); + console.log(`✓ Props files changed: ${propsChanged}`); - const testsChanged = files.some(file => file.filename.startsWith('test/powershell/') || file.filename.startsWith('test/tools/') || file.filename.startsWith('test/xUnit/')); + const testsChanged = files.some(file => file.startsWith('test/powershell/') || file.startsWith('test/tools/') || file.startsWith('test/xUnit/')); + console.log(`✓ Tests changed: ${testsChanged}`); - const mainSourceChanged = files.some(file => file.filename.startsWith('src/')); + const mainSourceChanged = files.some(file => file.startsWith('src/')); + console.log(`✓ Main source (src/) changed: ${mainSourceChanged}`); - const buildModuleChanged = files.some(file => file.filename.startsWith('build.psm1')); + const buildModuleChanged = files.some(file => file === 'build.psm1'); + console.log(`✓ build.psm1 changed: ${buildModuleChanged}`); - const globalConfigChanged = files.some(file => file.filename.startsWith('.globalconfig')) || files.some(file => file.filename.startsWith('nuget.config')) || files.some(file => file.filename.startsWith('global.json')); + const globalConfigChanged = files.some(file => file === '.globalconfig' || file === 'nuget.config' || file === 'global.json'); + console.log(`✓ Global config changed: ${globalConfigChanged}`); const packagingChanged = files.some(file => - file.filename === '.github/workflows/windows-ci.yml' || - file.filename === '.github/workflows/linux-ci.yml' || - file.filename.startsWith('assets/wix/') || - file.filename === 'PowerShell.Common.props' || - file.filename.match(/^src\/.*\.csproj$/) || - file.filename.startsWith('test/packaging/windows/') || - file.filename.startsWith('test/packaging/linux/') || - file.filename.startsWith('tools/packaging/') || - file.filename.startsWith('tools/wix/') + file === '.github/workflows/windows-ci.yml' || + file === '.github/workflows/linux-ci.yml' || + file.startsWith('assets/wix/') || + file === 'PowerShell.Common.props' || + file.match(/^src\/.*\.csproj$/) || + file.startsWith('test/packaging/windows/') || + file.startsWith('test/packaging/linux/') || + file.startsWith('tools/packaging/') || + file.startsWith('tools/wix/') ) || buildModuleChanged || globalConfigChanged || toolsCiPsm1Changed; + console.log(`→ Packaging changed: ${packagingChanged}`); const source = mainSourceChanged || toolsChanged || githubChanged || propsChanged || testsChanged || globalConfigChanged; + console.log(`→ Source (composite): ${source}`); + + core.endGroup(); core.setOutput('toolsChanged', toolsChanged); core.setOutput('githubChanged', githubChanged); @@ -114,16 +135,3 @@ runs: core.setOutput('globalConfigChanged', globalConfigChanged); core.setOutput('packagingChanged', packagingChanged); core.setOutput('source', source); - - - - name: Capture outputs - run: | - Write-Verbose -Verbose "source: ${{ steps.filter.outputs.source }}" - Write-Verbose -Verbose "github: ${{ steps.filter.outputs.githubChanged }}" - Write-Verbose -Verbose "tools: ${{ steps.filter.outputs.toolsChanged }}" - Write-Verbose -Verbose "props: ${{ steps.filter.outputs.propsChanged }}" - Write-Verbose -Verbose "tests: ${{ steps.filter.outputs.testsChanged }}" - Write-Verbose -Verbose "mainSource: ${{ steps.filter.outputs.mainSourceChanged }}" - Write-Verbose -Verbose "buildModule: ${{ steps.filter.outputs.buildModuleChanged }}" - Write-Verbose -Verbose "packaging: ${{ steps.filter.outputs.packagingChanged }}" - shell: pwsh From eda7e4a889482f6b898660c9dcea6a6e878502f2 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 30 Oct 2025 15:53:01 -0700 Subject: [PATCH 088/255] Make the experimental feature `PSFeedbackProvider` stable (#26343) --- .../host/msh/ConsoleHost.cs | 47 +-- .../ExperimentalFeature.cs | 5 - .../engine/hostifaces/HostUtilities.cs | 357 ------------------ .../resources/SuggestionStrings.resx | 9 - 4 files changed, 1 insertion(+), 417 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 7228cb31ee2..5f59540ecf4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -2568,14 +2568,7 @@ internal void Run(bool inputLoopIsNested) // Evaluate any suggestions if (previousResponseWasEmpty == false) { - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSFeedbackProvider)) - { - EvaluateFeedbacks(ui); - } - else - { - EvaluateSuggestions(ui); - } + EvaluateFeedbacks(ui); } // Then output the prompt @@ -2899,44 +2892,6 @@ private void EvaluateFeedbacks(ConsoleHostUserInterface ui) } } - private void EvaluateSuggestions(ConsoleHostUserInterface ui) - { - // Output any training suggestions - try - { - List suggestions = HostUtilities.GetSuggestion(_parent.Runspace); - - if (suggestions.Count > 0) - { - ui.WriteLine(); - } - - bool first = true; - foreach (string suggestion in suggestions) - { - if (!first) - ui.WriteLine(); - - ui.WriteLine(suggestion); - - first = false; - } - } - catch (TerminateException) - { - // A variable breakpoint may be hit by HostUtilities.GetSuggestion. The debugger throws TerminateExceptions to stop the execution - // of the current statement; we do not want to treat these exceptions as errors. - } - catch (Exception e) - { - // Catch-all OK. This is a third-party call-out. - ui.WriteErrorLine(e.Message); - - LocalRunspace localRunspace = (LocalRunspace)_parent.Runspace; - localRunspace.GetExecutionContext.AppendDollarError(e); - } - } - private string EvaluatePrompt() { string promptString = _promptExec.ExecuteCommandAndGetResultAsString("prompt", out _); diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 4ac98cb485e..1c17a22ae9a 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -21,7 +20,6 @@ public class ExperimentalFeature #region Const Members internal const string EngineSource = "PSEngine"; - internal const string PSFeedbackProvider = "PSFeedbackProvider"; internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); #endregion @@ -108,9 +106,6 @@ static ExperimentalFeature() new ExperimentalFeature( name: "PSLoadAssemblyFromNativeCode", description: "Expose an API to allow assembly loading from native code"), - new ExperimentalFeature( - name: PSFeedbackProvider, - description: "Replace the hard-coded suggestion framework with the extensible feedback provider"), new ExperimentalFeature( name: PSSerializeJSONLongEnumAsNumber, description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index 7c8ccd0d307..8e5d30be71f 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Internal; @@ -12,26 +10,10 @@ using System.Management.Automation.Subsystem.Feedback; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; - -using Microsoft.PowerShell.Commands; using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { - internal enum SuggestionMatchType - { - /// Match on a command. - Command = 0, - /// Match based on exception message. - Error = 1, - /// Match by running a script block. - Dynamic = 2, - - /// Match by fully qualified ErrorId. - ErrorId = 3 - } - #region Public HostUtilities Class /// @@ -43,31 +25,6 @@ public static class HostUtilities private static readonly char s_actionIndicator = HostSupportUnicode() ? '\u27a4' : '>'; - private static readonly string s_checkForCommandInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param() - - $foundSuggestion = $false - - if($lastError -and - ($lastError.Exception -is ""System.Management.Automation.CommandNotFoundException"")) - { - $escapedCommand = [System.Management.Automation.WildcardPattern]::Escape($lastError.TargetObject) - $foundSuggestion = @(Get-Command ($ExecutionContext.SessionState.Path.Combine(""."", $escapedCommand)) -ErrorAction Ignore).Count -gt 0 - } - - $foundSuggestion - "; - - private static readonly string s_createCommandExistsInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) - - $formatString -f $lastError.TargetObject,"".\$($lastError.TargetObject)"" - "; - - private static readonly List s_suggestions = InitializeSuggestions(); - private static bool HostSupportUnicode() { // Reference: https://github.com/zkat/supports-unicode/blob/main/src/lib.rs @@ -87,23 +44,6 @@ private static bool HostSupportUnicode() return ctype.EndsWith("UTF8") || ctype.EndsWith("UTF-8"); } - private static List InitializeSuggestions() - { - var suggestions = new List() - { - NewSuggestion( - id: 3, - category: "General", - matchType: SuggestionMatchType.Dynamic, - rule: ScriptBlock.CreateDelayParsedScriptBlock(s_checkForCommandInCurrentDirectoryScript, isProductCode: true), - suggestion: ScriptBlock.CreateDelayParsedScriptBlock(s_createCommandExistsInCurrentDirectoryScript, isProductCode: true), - suggestionArgs: new object[] { SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory_Legacy }, - enabled: true) - }; - - return suggestions; - } - #region GetProfileCommands /// /// Gets a PSObject whose base object is currentUserCurrentHost and with notes for the other 4 parameters. @@ -283,303 +223,6 @@ internal static string GetMaxLines(string source, int maxLines) return returnValue.ToString(); } - internal static List GetSuggestion(Runspace runspace) - { - if (runspace is not LocalRunspace localRunspace) - { - return new List(); - } - - // Get the last value of $? - bool questionMarkVariableValue = localRunspace.ExecutionContext.QuestionMarkVariableValue; - - // Get the last history item - History history = localRunspace.History; - HistoryInfo[] entries = history.GetEntries(-1, 1, true); - - if (entries.Length == 0) - return new List(); - - HistoryInfo lastHistory = entries[0]; - - // Get the last error - ArrayList errorList = (ArrayList)localRunspace.GetExecutionContext.DollarErrorVariable; - object lastError = null; - - if (errorList.Count > 0) - { - lastError = errorList[0] as Exception; - ErrorRecord lastErrorRecord = null; - - // The error was an actual ErrorRecord - if (lastError == null) - { - lastErrorRecord = errorList[0] as ErrorRecord; - } - else if (lastError is RuntimeException) - { - lastErrorRecord = ((RuntimeException)lastError).ErrorRecord; - } - - // If we got information about the error invocation, - // we can be more careful with the errors we pass along - if ((lastErrorRecord != null) && (lastErrorRecord.InvocationInfo != null)) - { - if (lastErrorRecord.InvocationInfo.HistoryId == lastHistory.Id) - lastError = lastErrorRecord; - else - lastError = null; - } - } - - Runspace oldDefault = null; - bool changedDefault = false; - if (Runspace.DefaultRunspace != runspace) - { - oldDefault = Runspace.DefaultRunspace; - changedDefault = true; - Runspace.DefaultRunspace = runspace; - } - - List suggestions = null; - - try - { - suggestions = GetSuggestion(lastHistory, lastError, errorList); - } - finally - { - if (changedDefault) - { - Runspace.DefaultRunspace = oldDefault; - } - } - - // Restore $? - localRunspace.ExecutionContext.QuestionMarkVariableValue = questionMarkVariableValue; - return suggestions; - } - - [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] - internal static List GetSuggestion(HistoryInfo lastHistory, object lastError, ArrayList errorList) - { - var returnSuggestions = new List(); - - PSModuleInfo invocationModule = new PSModuleInfo(true); - invocationModule.SessionState.PSVariable.Set("lastHistory", lastHistory); - invocationModule.SessionState.PSVariable.Set("lastError", lastError); - - int initialErrorCount = 0; - - // Go through all of the suggestions - foreach (Hashtable suggestion in s_suggestions) - { - initialErrorCount = errorList.Count; - - // Make sure the rule is enabled - if (!LanguagePrimitives.IsTrue(suggestion["Enabled"])) - continue; - - SuggestionMatchType matchType = (SuggestionMatchType)LanguagePrimitives.ConvertTo( - suggestion["MatchType"], - typeof(SuggestionMatchType), - CultureInfo.InvariantCulture); - - // If this is a dynamic match, evaluate the ScriptBlock - if (matchType == SuggestionMatchType.Dynamic) - { - object result = null; - - ScriptBlock evaluator = suggestion["Rule"] as ScriptBlock; - if (evaluator == null) - { - suggestion["Enabled"] = false; - - throw new ArgumentException( - SuggestionStrings.RuleMustBeScriptBlock, "Rule"); - } - - try - { - result = invocationModule.Invoke(evaluator, null); - } - catch (Exception) - { - // Catch-all OK. This is a third-party call-out. - suggestion["Enabled"] = false; - continue; - } - - // If it returned results, evaluate its suggestion - if (LanguagePrimitives.IsTrue(result)) - { - string suggestionText = GetSuggestionText(suggestion["Suggestion"], (object[])suggestion["SuggestionArgs"], invocationModule); - - if (!string.IsNullOrEmpty(suggestionText)) - { - string returnString = string.Format( - CultureInfo.CurrentCulture, - "Suggestion [{0},{1}]: {2}", - (int)suggestion["Id"], - (string)suggestion["Category"], - suggestionText); - - returnSuggestions.Add(returnString); - } - } - } - else - { - string matchText = string.Empty; - - // Otherwise, this is a Regex match against the - // command or error - if (matchType == SuggestionMatchType.Command) - { - matchText = lastHistory.CommandLine; - } - else if (matchType == SuggestionMatchType.Error) - { - if (lastError != null) - { - Exception lastException = lastError as Exception; - if (lastException != null) - { - matchText = lastException.Message; - } - else - { - matchText = lastError.ToString(); - } - } - } - else if (matchType == SuggestionMatchType.ErrorId) - { - if (lastError != null && lastError is ErrorRecord errorRecord) - { - matchText = errorRecord.FullyQualifiedErrorId; - } - } - else - { - suggestion["Enabled"] = false; - - throw new ArgumentException( - SuggestionStrings.InvalidMatchType, - "MatchType"); - } - - // If the text matches, evaluate the suggestion - if (Regex.IsMatch(matchText, (string)suggestion["Rule"], RegexOptions.IgnoreCase)) - { - string suggestionText = GetSuggestionText(suggestion["Suggestion"], (object[])suggestion["SuggestionArgs"], invocationModule); - - if (!string.IsNullOrEmpty(suggestionText)) - { - string returnString = string.Format( - CultureInfo.CurrentCulture, - "Suggestion [{0},{1}]: {2}", - (int)suggestion["Id"], - (string)suggestion["Category"], - suggestionText); - - returnSuggestions.Add(returnString); - } - } - } - - // If the rule generated an error, disable it - if (errorList.Count != initialErrorCount) - { - suggestion["Enabled"] = false; - } - } - - return returnSuggestions; - } - - /// - /// Create suggestion with string rule and scriptblock suggestion. - /// - /// Identifier for the suggestion. - /// Category for the suggestion. - /// Suggestion match type. - /// Rule to match. - /// Scriptblock to run that returns the suggestion. - /// Arguments to pass to suggestion scriptblock. - /// True if the suggestion is enabled. - /// Hashtable representing the suggestion. - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, string rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled) - { - Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - - result["Id"] = id; - result["Category"] = category; - result["MatchType"] = matchType; - result["Rule"] = rule; - result["Suggestion"] = suggestion; - result["SuggestionArgs"] = suggestionArgs; - result["Enabled"] = enabled; - - return result; - } - - /// - /// Create suggestion with scriptblock rule and suggestion. - /// - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, ScriptBlock rule, ScriptBlock suggestion, bool enabled) - { - Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - - result["Id"] = id; - result["Category"] = category; - result["MatchType"] = matchType; - result["Rule"] = rule; - result["Suggestion"] = suggestion; - result["Enabled"] = enabled; - - return result; - } - - /// - /// Create suggestion with scriptblock rule and scriptblock suggestion with arguments. - /// - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, ScriptBlock rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled) - { - Hashtable result = NewSuggestion(id, category, matchType, rule, suggestion, enabled); - result.Add("SuggestionArgs", suggestionArgs); - - return result; - } - - /// - /// Get suggestion text from suggestion scriptblock with arguments. - /// - private static string GetSuggestionText(object suggestion, object[] suggestionArgs, PSModuleInfo invocationModule) - { - if (suggestion is ScriptBlock) - { - ScriptBlock suggestionScript = (ScriptBlock)suggestion; - - object result = null; - try - { - result = invocationModule.Invoke(suggestionScript, suggestionArgs); - } - catch (Exception) - { - // Catch-all OK. This is a third-party call-out. - return string.Empty; - } - - return (string)LanguagePrimitives.ConvertTo(result, typeof(string), CultureInfo.CurrentCulture); - } - else - { - return (string)LanguagePrimitives.ConvertTo(suggestion, typeof(string), CultureInfo.CurrentCulture); - } - } - /// /// Returns the prompt used in remote sessions: "[machine]: basePrompt" /// diff --git a/src/System.Management.Automation/resources/SuggestionStrings.resx b/src/System.Management.Automation/resources/SuggestionStrings.resx index 9e325b2616c..39fbc3469f2 100644 --- a/src/System.Management.Automation/resources/SuggestionStrings.resx +++ b/src/System.Management.Automation/resources/SuggestionStrings.resx @@ -123,16 +123,7 @@ PowerShell does not load commands from the current location by default (see 'Get If you trust this command, run the following command instead: - - The command "{0}" was not found, but does exist in the current location. PowerShell does not load commands from the current location by default. If you trust this command, instead type: "{1}". See "get-help about_Command_Precedence" for more details. - The most similar commands are: - - Rule must be a ScriptBlock for dynamic match types. - - - MatchType must be 'Command', 'Error', or 'Dynamic'. - From c12322323dff4d37781e6480aa578bd5eb9ad88f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:08:40 -0700 Subject: [PATCH 089/255] Bump github/codeql-action from 4.31.0 to 4.31.2 (#26359) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 10b2f0893a3..a16a840a606 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index ffd2f3f1420..4246af344fd 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: sarif_file: results.sarif From 0c40a84abb39dfeb50c686e119bf8e18b12aab34 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:00:55 +0000 Subject: [PATCH 090/255] Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26350) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Travis Plunk (HE/HIM) --- .../merge-conflict-checker/README.md | 84 ++++++ .../merge-conflict-checker/action.yml | 36 +++ .../actions/test/linux-packaging/action.yml | 6 + ...rshell-automatic-variables.instructions.md | 159 ++++++++++++ ...rshell-module-organization.instructions.md | 201 +++++++++++++++ .github/workflows/linux-ci.yml | 58 +++++ .github/workflows/macos-ci.yml | 8 + .vsts-ci/templates/nanoserver.yml | 61 ----- test/infrastructure/ciModule.Tests.ps1 | 240 ++++++++++++++++++ tools/ci.psm1 | 210 ++++++++++++++- 10 files changed, 1001 insertions(+), 62 deletions(-) create mode 100644 .github/actions/infrastructure/merge-conflict-checker/README.md create mode 100644 .github/actions/infrastructure/merge-conflict-checker/action.yml create mode 100644 .github/instructions/powershell-automatic-variables.instructions.md create mode 100644 .github/instructions/powershell-module-organization.instructions.md delete mode 100644 .vsts-ci/templates/nanoserver.yml create mode 100644 test/infrastructure/ciModule.Tests.ps1 diff --git a/.github/actions/infrastructure/merge-conflict-checker/README.md b/.github/actions/infrastructure/merge-conflict-checker/README.md new file mode 100644 index 00000000000..aeae4a29b93 --- /dev/null +++ b/.github/actions/infrastructure/merge-conflict-checker/README.md @@ -0,0 +1,84 @@ +# Merge Conflict Checker + +This composite GitHub Action checks for Git merge conflict markers in files changed in pull requests. + +## Purpose + +Automatically detects leftover merge conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in pull request files to prevent them from being merged into the codebase. + +## Usage + +### In a Workflow + +```yaml +- name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" +``` + +### Complete Example + +```yaml +jobs: + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@v5 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" +``` + +## How It Works + +1. **File Detection**: Uses GitHub's API to get the list of files changed in the pull request +2. **Marker Scanning**: Reads each changed file and searches for the following markers: + - `<<<<<<<` (conflict start marker) + - `=======` (conflict separator) + - `>>>>>>>` (conflict end marker) +3. **Result Reporting**: + - If markers are found, the action fails and lists all affected files + - If no markers are found, the action succeeds + +## Outputs + +- `files-checked`: Number of files that were checked +- `conflicts-found`: Number of files containing merge conflict markers + +## Behavior + +- **Event Support**: Only works with `pull_request` events +- **File Handling**: + - Checks only files that were added, modified, or renamed + - Skips deleted files + - Skips binary/unreadable files + - Skips directories + +## Example Output + +When conflict markers are detected: + +``` +❌ Merge conflict markers detected in the following files: + - src/example.cs + Markers found: <<<<<<<, =======, >>>>>>> + - README.md + Markers found: <<<<<<<, =======, >>>>>>> + +Please resolve these conflicts before merging. +``` + +When no markers are found: + +``` +✅ No merge conflict markers found +``` + +## Integration + +This action is integrated into the `linux-ci.yml` workflow and runs automatically on all pull requests to ensure code quality before merging. diff --git a/.github/actions/infrastructure/merge-conflict-checker/action.yml b/.github/actions/infrastructure/merge-conflict-checker/action.yml new file mode 100644 index 00000000000..a86cfa5470a --- /dev/null +++ b/.github/actions/infrastructure/merge-conflict-checker/action.yml @@ -0,0 +1,36 @@ +name: 'Check for Merge Conflict Markers' +description: 'Checks for Git merge conflict markers in changed files for pull requests' +author: 'PowerShell Team' + +outputs: + files-checked: + description: 'Number of files checked for merge conflict markers' + value: ${{ steps.check.outputs.files-checked }} + conflicts-found: + description: 'Number of files with merge conflict markers' + value: ${{ steps.check.outputs.conflicts-found }} + +runs: + using: 'composite' + steps: + - name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + + - name: Check for merge conflict markers + id: check + shell: pwsh + env: + CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }} + run: | + # Get changed files from environment variable (secure against injection) + $changedFilesJson = $env:CHANGED_FILES_JSON + $changedFiles = $changedFilesJson | ConvertFrom-Json + + # Import ci.psm1 and run the check + Import-Module "$env:GITHUB_WORKSPACE/tools/ci.psm1" -Force + Test-MergeConflictMarker -File $changedFiles -WorkspacePath $env:GITHUB_WORKSPACE + +branding: + icon: 'alert-triangle' + color: 'red' diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index d0c72c7b035..3a61e0751c7 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -31,6 +31,12 @@ runs: Invoke-CIFinish shell: pwsh + - name: Install Pester + run: |- + Import-Module ./tools/ci.psm1 + Install-CIPester + shell: pwsh + - name: Validate Package Names run: |- # Run Pester tests to validate package names diff --git a/.github/instructions/powershell-automatic-variables.instructions.md b/.github/instructions/powershell-automatic-variables.instructions.md new file mode 100644 index 00000000000..5015847f41f --- /dev/null +++ b/.github/instructions/powershell-automatic-variables.instructions.md @@ -0,0 +1,159 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# PowerShell Automatic Variables - Naming Guidelines + +## Purpose + +This instruction provides guidelines for avoiding conflicts with PowerShell's automatic variables when writing PowerShell scripts and modules. + +## What Are Automatic Variables? + +PowerShell has built-in automatic variables that are created and maintained by PowerShell itself. Assigning values to these variables can cause unexpected behavior and side effects. + +## Common Automatic Variables to Avoid + +### Critical Variables (Never Use) + +- **`$matches`** - Contains the results of regular expression matches. Overwriting this can break regex operations. +- **`$_`** - Represents the current object in the pipeline. Only use within pipeline blocks. +- **`$PSItem`** - Alias for `$_`. Same rules apply. +- **`$args`** - Contains an array of undeclared parameters. Don't use as a regular variable. +- **`$input`** - Contains an enumerator of all input passed to a function. Don't reassign. +- **`$LastExitCode`** - Exit code of the last native command. Don't overwrite unless intentional. +- **`$?`** - Success status of the last command. Don't use as a variable name. +- **`$$`** - Last token in the last line received by the session. Don't use. +- **`$^`** - First token in the last line received by the session. Don't use. + +### Context Variables (Use with Caution) + +- **`$Error`** - Array of error objects. Don't replace, but can modify (e.g., `$Error.Clear()`). +- **`$PSBoundParameters`** - Parameters passed to the current function. Read-only. +- **`$MyInvocation`** - Information about the current command. Read-only. +- **`$PSCmdlet`** - Cmdlet object for advanced functions. Read-only. + +### Other Common Automatic Variables + +- `$true`, `$false`, `$null` - Boolean and null constants +- `$HOME`, `$PSHome`, `$PWD` - Path-related variables +- `$PID` - Process ID of the current PowerShell session +- `$Host` - Host application object +- `$PSVersionTable` - PowerShell version information + +For a complete list, see: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables + +## Best Practices + +### ❌ Bad - Using Automatic Variable Names + +```powershell +# Bad: $matches is an automatic variable used for regex capture groups +$matches = Select-String -Path $file -Pattern $pattern + +# Bad: $args is an automatic variable for undeclared parameters +$args = Get-ChildItem + +# Bad: $input is an automatic variable for pipeline input +$input = Read-Host "Enter value" +``` + +### ✅ Good - Using Descriptive Alternative Names + +```powershell +# Good: Use descriptive names that avoid conflicts +$matchedLines = Select-String -Path $file -Pattern $pattern + +# Good: Use specific names for arguments +$arguments = Get-ChildItem + +# Good: Use specific names for user input +$userInput = Read-Host "Enter value" +``` + +## Naming Alternatives + +When you encounter a situation where you might use an automatic variable name, use these alternatives: + +| Avoid | Use Instead | +|-------|-------------| +| `$matches` | `$matchedLines`, `$matchResults`, `$regexMatches` | +| `$args` | `$arguments`, `$parameters`, `$commandArgs` | +| `$input` | `$userInput`, `$inputValue`, `$inputData` | +| `$_` (outside pipeline) | Use a named parameter or explicit variable | +| `$Error` (reassignment) | Don't reassign; use `$Error.Clear()` if needed | + +## How to Check + +### PSScriptAnalyzer Rule + +PSScriptAnalyzer has a built-in rule that detects assignments to automatic variables: + +```powershell +# This will trigger PSAvoidAssignmentToAutomaticVariable +$matches = Get-Something +``` + +**Rule ID**: PSAvoidAssignmentToAutomaticVariable + +### Manual Review + +When writing PowerShell code, always: +1. Avoid variable names that match PowerShell keywords or automatic variables +2. Use descriptive, specific names that clearly indicate the variable's purpose +3. Run PSScriptAnalyzer on your code before committing +4. Review code for variable naming during PR reviews + +## Examples from the Codebase + +### Example 1: Regex Matching + +```powershell +# ❌ Bad - Overwrites automatic $matches variable +$matches = [regex]::Matches($content, $pattern) + +# ✅ Good - Uses descriptive name +$regexMatches = [regex]::Matches($content, $pattern) +``` + +### Example 2: Select-String Results + +```powershell +# ❌ Bad - Conflicts with automatic $matches +$matches = Select-String -Path $file -Pattern $pattern + +# ✅ Good - Clear and specific +$matchedLines = Select-String -Path $file -Pattern $pattern +``` + +### Example 3: Collecting Arguments + +```powershell +# ❌ Bad - Conflicts with automatic $args +function Process-Items { + $args = $MyItems + # ... process items +} + +# ✅ Good - Descriptive parameter name +function Process-Items { + [CmdletBinding()] + param( + [Parameter(ValueFromRemainingArguments)] + [string[]]$Items + ) + # ... process items +} +``` + +## References + +- [PowerShell Automatic Variables Documentation](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables) +- [PSScriptAnalyzer Rules](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/docs/Rules/README.md) +- [PowerShell Best Practices](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines) + +## Summary + +**Key Takeaway**: Always use descriptive, specific variable names that clearly indicate their purpose and avoid conflicts with PowerShell's automatic variables. When in doubt, choose a longer, more descriptive name over a short one that might conflict. diff --git a/.github/instructions/powershell-module-organization.instructions.md b/.github/instructions/powershell-module-organization.instructions.md new file mode 100644 index 00000000000..461d19fb5df --- /dev/null +++ b/.github/instructions/powershell-module-organization.instructions.md @@ -0,0 +1,201 @@ +--- +applyTo: + - "tools/ci.psm1" + - "build.psm1" + - "tools/packaging/**/*.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Guidelines for PowerShell Code Organization + +## When to Move Code from YAML to PowerShell Modules + +PowerShell code in GitHub Actions YAML files should be kept minimal. Move code to a module when: + +### Size Threshold +- **More than ~30 lines** of PowerShell in a YAML file step +- **Any use of .NET types** like `[regex]`, `[System.IO.Path]`, etc. +- **Complex logic** requiring multiple nested loops or conditionals +- **Reusable functionality** that might be needed elsewhere + +### Indicators to Move Code +1. Using .NET type accelerators (`[regex]`, `[PSCustomObject]`, etc.) +2. Complex string manipulation or parsing +3. File system operations beyond basic reads/writes +4. Logic that would benefit from unit testing +5. Code that's difficult to read/maintain in YAML format + +## Which Module to Use + +### ci.psm1 (`tools/ci.psm1`) +**Purpose**: CI/CD-specific operations and workflows + +**Use for**: +- Build orchestration (invoking builds, tests, packaging) +- CI environment setup and configuration +- Test execution and result processing +- Artifact handling and publishing +- CI-specific validations and checks +- Environment variable management for CI + +**Examples**: +- `Invoke-CIBuild` - Orchestrates build process +- `Invoke-CITest` - Runs Pester tests +- `Test-MergeConflictMarker` - Validates files for conflicts +- `Set-BuildVariable` - Manages CI variables + +**When NOT to use**: +- Core build operations (use build.psm1) +- Package creation logic (use packaging.psm1) +- Platform-specific build steps + +### build.psm1 (`build.psm1`) +**Purpose**: Core build operations and utilities + +**Use for**: +- Compiling source code +- Resource generation +- Build configuration management +- Core build utilities (New-PSOptions, Get-PSOutput, etc.) +- Bootstrap operations +- Cross-platform build helpers + +**Examples**: +- `Start-PSBuild` - Main build function +- `Start-PSBootstrap` - Bootstrap dependencies +- `New-PSOptions` - Create build configuration +- `Start-ResGen` - Generate resources + +**When NOT to use**: +- CI workflow orchestration (use ci.psm1) +- Package creation (use packaging.psm1) +- Test execution + +### packaging.psm1 (`tools/packaging/packaging.psm1`) +**Purpose**: Package creation and distribution + +**Use for**: +- Creating distribution packages (MSI, RPM, DEB, etc.) +- Package-specific metadata generation +- Package signing operations +- Platform-specific packaging logic + +**Examples**: +- `Start-PSPackage` - Create packages +- `New-MSIPackage` - Create Windows MSI +- `New-DotnetSdkContainerFxdPackage` - Create container packages + +**When NOT to use**: +- Building binaries (use build.psm1) +- Running tests (use ci.psm1) +- General utilities + +## Best Practices + +### Keep YAML Minimal +```yaml +# ❌ Bad - too much logic in YAML +- name: Check files + shell: pwsh + run: | + $files = Get-ChildItem -Recurse + foreach ($file in $files) { + $content = Get-Content $file -Raw + if ($content -match $pattern) { + # ... complex processing ... + } + } + +# ✅ Good - call function from module +- name: Check files + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Test-SomeCondition -Path ${{ github.workspace }} +``` + +### Document Functions +Always include comment-based help for functions: +```powershell +function Test-MyFunction +{ + <# + .SYNOPSIS + Brief description + .DESCRIPTION + Detailed description + .PARAMETER ParameterName + Parameter description + .EXAMPLE + Test-MyFunction -ParameterName Value + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $ParameterName + ) + # Implementation +} +``` + +### Error Handling +Use proper error handling in modules: +```powershell +try { + # Operation +} +catch { + Write-Error "Detailed error message: $_" + throw +} +``` + +### Verbose Output +Use `Write-Verbose` for debugging information: +```powershell +Write-Verbose "Processing file: $filePath" +``` + +## Module Dependencies + +- **ci.psm1** imports both `build.psm1` and `packaging.psm1` +- **build.psm1** is standalone (minimal dependencies) +- **packaging.psm1** imports `build.psm1` + +When adding new functions, consider these import relationships to avoid circular dependencies. + +## Testing Modules + +Functions in modules should be testable: +```powershell +# Test locally +Import-Module ./tools/ci.psm1 -Force +Test-MyFunction -Parameter Value + +# Can be unit tested with Pester +Describe "Test-MyFunction" { + It "Should return expected result" { + # Test implementation + } +} +``` + +## Migration Checklist + +When moving code from YAML to a module: + +1. ✅ Determine which module is appropriate (ci, build, or packaging) +2. ✅ Create function with proper parameter validation +3. ✅ Add comment-based help documentation +4. ✅ Use `[CmdletBinding()]` for advanced function features +5. ✅ Include error handling +6. ✅ Add verbose output for debugging +7. ✅ Test the function independently +8. ✅ Update YAML to call the new function +9. ✅ Verify the workflow still works end-to-end + +## References + +- PowerShell Advanced Functions: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced +- Comment-Based Help: https://learn.microsoft.com/powershell/scripting/developer/help/writing-help-for-windows-powershell-scripts-and-functions diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 8dc0e738ffd..2058bd61568 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -67,6 +67,20 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && (startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell') + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@v5 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" + ci_build: name: Build PowerShell runs-on: ubuntu-latest @@ -158,6 +172,48 @@ jobs: runner_os: ubuntu-latest test_results_artifact_name: testResults-xunit + infrastructure_tests: + name: Infrastructure Tests + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + + - name: Install Pester + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Install-CIPester + + - name: Run Infrastructure Tests + shell: pwsh + run: | + $testResultsFolder = Join-Path $PWD "testResults" + New-Item -ItemType Directory -Path $testResultsFolder -Force | Out-Null + + $config = New-PesterConfiguration + $config.Run.Path = './test/infrastructure/' + $config.Run.PassThru = $true + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'NUnitXml' + $config.TestResult.OutputPath = "$testResultsFolder/InfrastructureTests.xml" + $config.Output.Verbosity = 'Detailed' + + $result = Invoke-Pester -Configuration $config + + if ($result.FailedCount -gt 0 -or $result.Result -eq 'Failed') { + throw "Infrastructure tests failed" + } + + - name: Publish Test Results + uses: "./.github/actions/test/process-pester-results" + if: always() + with: + name: "InfrastructureTests" + testResultsFolder: "${{ github.workspace }}/testResults" + ## Temporarily disable the CodeQL analysis on Linux as it doesn't work for .NET SDK 10-rc.2. # analyze: # name: CodeQL Analysis @@ -180,6 +236,8 @@ jobs: - linux_test_unelevated_ci - linux_test_unelevated_others - linux_packaging + - merge_conflict_check + - infrastructure_tests # - analyze if: always() uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 8a80b79f1c0..2ee96079049 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -187,6 +187,14 @@ jobs: $macOSRuntime = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'osx-arm64' } else { 'osx-x64' } Start-PSPackage -Type osxpkg -ReleaseTag $releaseTag -MacOSRuntime $macOSRuntime -SkipReleaseChecks shell: pwsh + + - name: Install Pester + if: success() + run: |- + Import-Module ./tools/ci.psm1 + Install-CIPester + shell: pwsh + - name: Test package contents if: success() run: |- diff --git a/.vsts-ci/templates/nanoserver.yml b/.vsts-ci/templates/nanoserver.yml deleted file mode 100644 index ae9f639b3b2..00000000000 --- a/.vsts-ci/templates/nanoserver.yml +++ /dev/null @@ -1,61 +0,0 @@ -parameters: - vmImage: 'windows-latest' - jobName: 'Nanoserver_Tests' - continueOnError: false - -jobs: - -- job: ${{ parameters.jobName }} - variables: - scriptName: ${{ parameters.scriptName }} - - pool: - vmImage: ${{ parameters.vmImage }} - - displayName: ${{ parameters.jobName }} - - steps: - - script: | - set - displayName: Capture Environment - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - - pwsh: | - Install-module Pester -Scope CurrentUser -Force -MaximumVersion 4.99 - displayName: 'Install Pester' - continueOnError: true - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $path = split-path -path $options.Output - Write-Verbose "Path: '$path'" -Verbose - $rootPath = split-Path -path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - Invoke-Pester -Path ./test/nanoserver -OutputFormat NUnitXml -OutputFile ./test-nanoserver.xml - displayName: Test - condition: succeeded() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - displayName: Publish Nanoserver Test Results **\test*.xml - inputs: - testRunner: NUnit - testResultsFiles: '**\test*.xml' - testRunTitle: nanoserver - mergeTestResults: true - failTaskOnFailedTests: true diff --git a/test/infrastructure/ciModule.Tests.ps1 b/test/infrastructure/ciModule.Tests.ps1 new file mode 100644 index 00000000000..f88d5787fc9 --- /dev/null +++ b/test/infrastructure/ciModule.Tests.ps1 @@ -0,0 +1,240 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# NOTE: This test file tests the Test-MergeConflictMarker function which detects Git merge conflict markers. +# IMPORTANT: Do NOT use here-strings or literal conflict markers (e.g., "<<<<<<<", "=======", ">>>>>>>") +# in this file, as they will trigger conflict marker detection in CI pipelines. +# Instead, use string multiplication (e.g., '<' * 7) to dynamically generate these markers at runtime. + +Describe "Test-MergeConflictMarker" { + BeforeAll { + # Import the module + Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force + + # Create a temporary test workspace + $script:testWorkspace = Join-Path $TestDrive "workspace" + New-Item -ItemType Directory -Path $script:testWorkspace -Force | Out-Null + + # Create temporary output files + $script:testOutputPath = Join-Path $TestDrive "outputs.txt" + $script:testSummaryPath = Join-Path $TestDrive "summary.md" + } + + AfterEach { + # Clean up test files after each test + if (Test-Path $script:testWorkspace) { + Get-ChildItem $script:testWorkspace -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + } + Remove-Item $script:testOutputPath -Force -ErrorAction SilentlyContinue + Remove-Item $script:testSummaryPath -Force -ErrorAction SilentlyContinue + } + + Context "When no files are provided" { + It "Should handle empty file array" { + # The function parameter has Mandatory validation which rejects empty arrays by design + # This test verifies that behavior + $emptyArray = @() + { Test-MergeConflictMarker -File $emptyArray -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw -ExpectedMessage "*empty array*" + } + } + + Context "When files have no conflicts" { + It "Should pass for clean files" { + $testFile = Join-Path $script:testWorkspace "clean.txt" + "This is a clean file" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("clean.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=1" + $outputs | Should -Contain "conflicts-found=0" + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "No Conflicts Found" + } + } + + Context "When files have conflict markers" { + It "Should detect <<<<<<< marker" { + $testFile = Join-Path $script:testWorkspace "conflict1.txt" + "Some content`n" + ('<' * 7) + " HEAD`nConflicting content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict1.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=1" + $outputs | Should -Contain "conflicts-found=1" + } + + It "Should detect ======= marker" { + $testFile = Join-Path $script:testWorkspace "conflict2.txt" + "Some content`n" + ('=' * 7) + "`nMore content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict2.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + } + + It "Should detect >>>>>>> marker" { + $testFile = Join-Path $script:testWorkspace "conflict3.txt" + "Some content`n" + ('>' * 7) + " branch-name`nMore content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict3.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + } + + It "Should detect multiple markers in one file" { + $testFile = Join-Path $script:testWorkspace "conflict4.txt" + $content = "Some content`n" + ('<' * 7) + " HEAD`nContent A`n" + ('=' * 7) + "`nContent B`n" + ('>' * 7) + " branch`nMore content" + $content | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict4.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "Conflicts Detected" + $summary | Should -Match "conflict4.txt" + } + + It "Should detect conflicts in multiple files" { + $testFile1 = Join-Path $script:testWorkspace "conflict5.txt" + ('<' * 7) + " HEAD" | Out-File $testFile1 -Encoding utf8 + + $testFile2 = Join-Path $script:testWorkspace "conflict6.txt" + ('=' * 7) | Out-File $testFile2 -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict5.txt", "conflict6.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=2" + $outputs | Should -Contain "conflicts-found=2" + } + } + + Context "When markers are not at line start" { + It "Should not detect markers in middle of line" { + $testFile = Join-Path $script:testWorkspace "notconflict.txt" + "This line has <<<<<<< in the middle" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("notconflict.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + + It "Should not detect markers with wrong number of characters" { + $testFile = Join-Path $script:testWorkspace "wrongcount.txt" + ('<' * 6) + " Only 6`n" + ('<' * 8) + " 8 characters" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("wrongcount.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + } + + Context "When handling special file scenarios" { + It "Should skip non-existent files" { + Test-MergeConflictMarker -File @("nonexistent.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=0" + } + + It "Should handle absolute paths" { + $testFile = Join-Path $script:testWorkspace "absolute.txt" + "Clean content" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @($testFile) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + + It "Should handle mixed relative and absolute paths" { + $testFile1 = Join-Path $script:testWorkspace "relative.txt" + "Clean" | Out-File $testFile1 -Encoding utf8 + + $testFile2 = Join-Path $script:testWorkspace "absolute.txt" + "Clean" | Out-File $testFile2 -Encoding utf8 + + Test-MergeConflictMarker -File @("relative.txt", $testFile2) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=2" + $outputs | Should -Contain "conflicts-found=0" + } + } + + Context "When summary and output generation" { + It "Should generate proper GitHub Actions outputs format" { + $testFile = Join-Path $script:testWorkspace "test.txt" + "Clean file" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("test.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Where-Object {$_ -match "^files-checked=\d+$"} | Should -Not -BeNullOrEmpty + $outputs | Where-Object {$_ -match "^conflicts-found=\d+$"} | Should -Not -BeNullOrEmpty + } + + It "Should generate markdown summary with conflict details" { + $testFile = Join-Path $script:testWorkspace "marked.txt" + $content = "Line 1`n" + ('<' * 7) + " HEAD`nLine 3`n" + ('=' * 7) + "`nLine 5" + $content | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("marked.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "# Merge Conflict Marker Check Results" + $summary | Should -Match "marked.txt" + $summary | Should -Match "\| Line \| Marker \|" + } + } +} + +Describe "Install-CIPester" { + BeforeAll { + # Import the module + Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force + } + + Context "When checking function exists" { + It "Should export Install-CIPester function" { + $function = Get-Command Install-CIPester -ErrorAction SilentlyContinue + $function | Should -Not -BeNullOrEmpty + $function.ModuleName | Should -Be 'ci' + } + + It "Should have expected parameters" { + $function = Get-Command Install-CIPester + $function.Parameters.Keys | Should -Contain 'MinimumVersion' + $function.Parameters.Keys | Should -Contain 'MaximumVersion' + $function.Parameters.Keys | Should -Contain 'Force' + } + + It "Should accept version parameters" { + $function = Get-Command Install-CIPester + $function.Parameters['MinimumVersion'].ParameterType.Name | Should -Be 'String' + $function.Parameters['MaximumVersion'].ParameterType.Name | Should -Be 'String' + $function.Parameters['Force'].ParameterType.Name | Should -Be 'SwitchParameter' + } + } + + Context "When validating real execution" { + # These tests only run in CI where we can safely install/test Pester + + It "Should successfully run without errors when Pester exists" { + if (!$env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + } + + { Install-CIPester -ErrorAction Stop } | Should -Not -Throw + } + + It "Should accept custom version parameters" { + if (!$env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + } + + { Install-CIPester -MinimumVersion '4.0.0' -MaximumVersion '5.99.99' -ErrorAction Stop } | Should -Not -Throw + } + } +} + diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 478435e8543..bcc816cc918 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -228,6 +228,45 @@ function Invoke-CIxUnit } } +# Install Pester module if not already installed with a compatible version +function Install-CIPester +{ + [CmdletBinding()] + param( + [string]$MinimumVersion = '5.0.0', + [string]$MaximumVersion = '5.99.99', + [switch]$Force + ) + + Write-Verbose "Checking for Pester module (required: $MinimumVersion - $MaximumVersion)" -Verbose + + # Check if a compatible version of Pester is already installed + $installedPester = Get-Module -Name Pester -ListAvailable | + Where-Object { $_.Version -ge $MinimumVersion -and $_.Version -le $MaximumVersion } | + Sort-Object -Property Version -Descending | + Select-Object -First 1 + + if ($installedPester -and -not $Force) { + Write-Host "Pester version $($installedPester.Version) is already installed and meets requirements" -ForegroundColor Green + return + } + + if ($Force) { + Write-Host "Installing Pester module (forced)" -ForegroundColor Yellow + } else { + Write-Host "Installing Pester module" -ForegroundColor Yellow + } + + try { + Install-Module -Name Pester -Force -SkipPublisherCheck -MaximumVersion $MaximumVersion -ErrorAction Stop + Write-Host "Successfully installed Pester module" -ForegroundColor Green + } + catch { + Write-Error "Failed to install Pester module: $_" + throw + } +} + # Implement CI 'Test_script' function Invoke-CITest { @@ -621,7 +660,7 @@ function Invoke-CIFinish # Install the latest Pester and import it $maximumPesterVersion = '4.99' - Install-Module Pester -Force -SkipPublisherCheck -MaximumVersion $maximumPesterVersion + Install-CIPester -MinimumVersion '4.0.0' -MaximumVersion $maximumPesterVersion -Force Import-Module Pester -Force -MaximumVersion $maximumPesterVersion $testResultPath = Join-Path -Path $env:TEMP -ChildPath "win-package-$channel-$runtime.xml" @@ -977,3 +1016,172 @@ function Invoke-InitializeContainerStage { Write-Host "##vso[build.addbuildtag]$($selectedImage.JobName)" } } + +Function Test-MergeConflictMarker +{ + <# + .SYNOPSIS + Checks files for Git merge conflict markers and outputs results for GitHub Actions. + .DESCRIPTION + Scans the specified files for Git merge conflict markers (<<<<<<<, =======, >>>>>>>) + and generates console output, GitHub Actions outputs, and job summary. + Designed for use in GitHub Actions workflows. + .PARAMETER File + Array of file paths (relative or absolute) to check for merge conflict markers. + .PARAMETER WorkspacePath + Base workspace path for resolving relative paths. Defaults to current directory. + .PARAMETER OutputPath + Path to write GitHub Actions outputs. Defaults to $env:GITHUB_OUTPUT. + .PARAMETER SummaryPath + Path to write GitHub Actions job summary. Defaults to $env:GITHUB_STEP_SUMMARY. + .EXAMPLE + Test-MergeConflictMarker -File @('file1.txt', 'file2.cs') -WorkspacePath $env:GITHUB_WORKSPACE + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string[]] $File, + + [Parameter()] + [string] $WorkspacePath = $PWD, + + [Parameter()] + [string] $OutputPath = $env:GITHUB_OUTPUT, + + [Parameter()] + [string] $SummaryPath = $env:GITHUB_STEP_SUMMARY + ) + + Write-Host "Starting merge conflict marker check..." -ForegroundColor Cyan + + Write-Host "Checking $($File.Count) changed files for merge conflict markers" -ForegroundColor Cyan + + # Convert relative paths to absolute paths for processing + $absolutePaths = $File | ForEach-Object { + if ([System.IO.Path]::IsPathRooted($_)) { + $_ + } else { + Join-Path $WorkspacePath $_ + } + } + + $filesWithConflicts = @() + $filesChecked = 0 + + foreach ($filePath in $absolutePaths) { + # Check if file exists (might be deleted) + if (-not (Test-Path $filePath)) { + Write-Verbose " Skipping deleted file: $filePath" + continue + } + + # Skip binary files and directories + if ((Get-Item $filePath) -is [System.IO.DirectoryInfo]) { + continue + } + + $filesChecked++ + + # Get relative path for display + $relativePath = if ($WorkspacePath -and $filePath.StartsWith($WorkspacePath)) { + $filePath.Substring($WorkspacePath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + } else { + $filePath + } + + Write-Host " Checking: $relativePath" -ForegroundColor Gray + + # Search for conflict markers using Select-String + try { + # Git conflict markers are 7 characters followed by a space or end of line + # Regex pattern breakdown: + # ^ - Matches the start of a line + # (<{7}|={7}|>{7}) - Matches exactly 7 consecutive '<', '=', or '>' characters (Git conflict markers) + # (\s|$) - Ensures the marker is followed by whitespace or end of line + $pattern = '^(<{7}|={7}|>{7})(\s|$)' + $matchedLines = Select-String -Path $filePath -Pattern $pattern -AllMatches -ErrorAction Stop + + if ($matchedLines) { + # Collect marker details with line numbers (Select-String provides LineNumber automatically) + $markerDetails = @() + + foreach ($match in $matchedLines) { + $markerDetails += [PSCustomObject]@{ + Marker = $match.Matches[0].Groups[1].Value + Line = $match.LineNumber + } + } + + $filesWithConflicts += [PSCustomObject]@{ + File = $relativePath + MarkerDetails = $markerDetails + } + + Write-Host " ❌ CONFLICT MARKERS FOUND in $relativePath" -ForegroundColor Red + foreach ($detail in $markerDetails) { + Write-Host " Line $($detail.Line): $($detail.Marker)" -ForegroundColor Red + } + } + } + catch { + # Skip files that can't be read (likely binary) + Write-Verbose " Skipping unreadable file: $relativePath" + } + } + + # Output results to GitHub Actions + if ($OutputPath) { + "files-checked=$filesChecked" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + "conflicts-found=$($filesWithConflicts.Count)" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + } + + Write-Host "`nSummary:" -ForegroundColor Cyan + Write-Host " Files checked: $filesChecked" -ForegroundColor Cyan + Write-Host " Files with conflicts: $($filesWithConflicts.Count)" -ForegroundColor Cyan + + # Create GitHub Actions job summary + if ($SummaryPath) { + $summaryContent = @" +# Merge Conflict Marker Check Results + +## Summary +- **Files Checked:** $filesChecked +- **Files with Conflicts:** $($filesWithConflicts.Count) + +"@ + + if ($filesWithConflicts.Count -gt 0) { + Write-Host "`n❌ Merge conflict markers detected in the following files:" -ForegroundColor Red + + $summaryContent += "`n## ❌ Conflicts Detected`n`n" + $summaryContent += "The following files contain merge conflict markers:`n`n" + + foreach ($fileInfo in $filesWithConflicts) { + Write-Host " - $($fileInfo.File)" -ForegroundColor Red + + $summaryContent += "### 📄 ``$($fileInfo.File)```n`n" + $summaryContent += "| Line | Marker |`n" + $summaryContent += "|------|--------|`n" + + foreach ($detail in $fileInfo.MarkerDetails) { + Write-Host " Line $($detail.Line): $($detail.Marker)" -ForegroundColor Red + $summaryContent += "| $($detail.Line) | ``$($detail.Marker)`` |`n" + } + $summaryContent += "`n" + } + + $summaryContent += "`n**Action Required:** Please resolve these conflicts before merging.`n" + Write-Host "`nPlease resolve these conflicts before merging." -ForegroundColor Red + } else { + Write-Host "`n✅ No merge conflict markers found" -ForegroundColor Green + $summaryContent += "`n## ✅ No Conflicts Found`n`nAll checked files are free of merge conflict markers.`n" + } + + $summaryContent | Out-File -FilePath $SummaryPath -Encoding utf8 + } + + # Exit with error if conflicts found + if ($filesWithConflicts.Count -gt 0) { + throw "Merge conflict markers detected in $($filesWithConflicts.Count) file(s)" + } +} From 8851ab8170a770d9dfeedf8bf08bd9a160f2207b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:52:40 +0000 Subject: [PATCH 091/255] Fix merge conflict checker for empty file lists and filter *.cs files (#26365) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .../merge-conflict-checker/README.md | 2 + .../merge-conflict-checker/action.yml | 3 +- test/infrastructure/ciModule.Tests.ps1 | 14 +++-- tools/ci.psm1 | 62 +++++++++++++++++-- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/.github/actions/infrastructure/merge-conflict-checker/README.md b/.github/actions/infrastructure/merge-conflict-checker/README.md index aeae4a29b93..b53d6f99964 100644 --- a/.github/actions/infrastructure/merge-conflict-checker/README.md +++ b/.github/actions/infrastructure/merge-conflict-checker/README.md @@ -56,8 +56,10 @@ jobs: - **File Handling**: - Checks only files that were added, modified, or renamed - Skips deleted files + - **Filters out `*.cs` files** (C# files are excluded from merge conflict checking) - Skips binary/unreadable files - Skips directories +- **Empty File List**: Gracefully handles cases where no files need checking (e.g., PRs that only delete files) ## Example Output diff --git a/.github/actions/infrastructure/merge-conflict-checker/action.yml b/.github/actions/infrastructure/merge-conflict-checker/action.yml index a86cfa5470a..41c7d2ad941 100644 --- a/.github/actions/infrastructure/merge-conflict-checker/action.yml +++ b/.github/actions/infrastructure/merge-conflict-checker/action.yml @@ -25,7 +25,8 @@ runs: run: | # Get changed files from environment variable (secure against injection) $changedFilesJson = $env:CHANGED_FILES_JSON - $changedFiles = $changedFilesJson | ConvertFrom-Json + # Ensure we always have an array (ConvertFrom-Json returns null for empty JSON arrays) + $changedFiles = @($changedFilesJson | ConvertFrom-Json) # Import ci.psm1 and run the check Import-Module "$env:GITHUB_WORKSPACE/tools/ci.psm1" -Force diff --git a/test/infrastructure/ciModule.Tests.ps1 b/test/infrastructure/ciModule.Tests.ps1 index f88d5787fc9..b7320ff49b7 100644 --- a/test/infrastructure/ciModule.Tests.ps1 +++ b/test/infrastructure/ciModule.Tests.ps1 @@ -30,11 +30,17 @@ Describe "Test-MergeConflictMarker" { } Context "When no files are provided" { - It "Should handle empty file array" { - # The function parameter has Mandatory validation which rejects empty arrays by design - # This test verifies that behavior + It "Should handle empty file array gracefully" { + # The function now accepts empty arrays to handle cases like delete-only PRs $emptyArray = @() - { Test-MergeConflictMarker -File $emptyArray -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw -ExpectedMessage "*empty array*" + Test-MergeConflictMarker -File $emptyArray -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=0" + $outputs | Should -Contain "conflicts-found=0" + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "No Files to Check" } } diff --git a/tools/ci.psm1 b/tools/ci.psm1 index bcc816cc918..b9d150ceacd 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -1039,8 +1039,9 @@ Function Test-MergeConflictMarker #> [CmdletBinding()] param( - [Parameter(Mandatory)] - [string[]] $File, + [Parameter()] + [AllowEmptyCollection()] + [string[]] $File = @(), [Parameter()] [string] $WorkspacePath = $PWD, @@ -1054,10 +1055,63 @@ Function Test-MergeConflictMarker Write-Host "Starting merge conflict marker check..." -ForegroundColor Cyan - Write-Host "Checking $($File.Count) changed files for merge conflict markers" -ForegroundColor Cyan + # Helper function to write outputs when no files to check + function Write-NoFilesOutput { + param( + [string]$Message, + [string]$OutputPath, + [string]$SummaryPath + ) + + # Output results to GitHub Actions + if ($OutputPath) { + "files-checked=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + "conflicts-found=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + } + + # Create GitHub Actions job summary + if ($SummaryPath) { + $summaryContent = @" +# Merge Conflict Marker Check Results + +## Summary +- **Files Checked:** 0 +- **Files with Conflicts:** 0 + +## ℹ️ No Files to Check + +$Message + +"@ + $summaryContent | Out-File -FilePath $SummaryPath -Encoding utf8 + } + } + + # Handle empty file list (e.g., when PR only deletes files) + if ($File.Count -eq 0) { + Write-Host "No files to check (empty file list)" -ForegroundColor Yellow + Write-NoFilesOutput -Message "No files were provided for checking (this can happen when a PR only deletes files)." -OutputPath $OutputPath -SummaryPath $SummaryPath + return + } + + # Filter out *.cs files from merge conflict checking + $filesToCheck = @($File | Where-Object { $_ -notlike "*.cs" }) + $filteredCount = $File.Count - $filesToCheck.Count + + if ($filteredCount -gt 0) { + Write-Host "Filtered out $filteredCount *.cs file(s) from merge conflict checking" -ForegroundColor Yellow + } + + if ($filesToCheck.Count -eq 0) { + Write-Host "No files to check after filtering (all files were *.cs)" -ForegroundColor Yellow + Write-NoFilesOutput -Message "All $filteredCount file(s) were filtered out (*.cs files are excluded from merge conflict checking)." -OutputPath $OutputPath -SummaryPath $SummaryPath + return + } + + Write-Host "Checking $($filesToCheck.Count) changed files for merge conflict markers" -ForegroundColor Cyan # Convert relative paths to absolute paths for processing - $absolutePaths = $File | ForEach-Object { + $absolutePaths = $filesToCheck | ForEach-Object { if ([System.IO.Path]::IsPathRooted($_)) { $_ } else { From 8814b04bb441470dc189a7cc4582d3982508a6fb Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 31 Oct 2025 15:56:08 -0700 Subject: [PATCH 092/255] Delete this way of collecting feedback (#26364) --- .../IssueManagement.IssueFeedbackForm.yml | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/policies/IssueManagement.IssueFeedbackForm.yml diff --git a/.github/policies/IssueManagement.IssueFeedbackForm.yml b/.github/policies/IssueManagement.IssueFeedbackForm.yml deleted file mode 100644 index f42be69ab1e..00000000000 --- a/.github/policies/IssueManagement.IssueFeedbackForm.yml +++ /dev/null @@ -1,22 +0,0 @@ -id: IssueManagement.IssueFeedbackForm -name: GitOps.PullRequestIssueManagement -description: Bot to request the feedback for how we are doing in the repo, replies on closed PRs and issues that are NOT labeled with no activity -owner: -resource: repository -disabled: false -where: -configuration: - resourceManagementConfiguration: - eventResponderTasks: - - if: - - not: - hasLabel: - label: Resolution-No Activity - - isAction: - action: Closed - then: - - addReply: - reply: " 📣 Hey @${issueAuthor}, how did we do? We would love to hear your feedback with the link below! 🗣️ \n\n🔗 https://aka.ms/PSRepoFeedback " - triggerOnOwnActions: true -onFailure: -onSuccess: From c8f26fec50411558e46b0b1ce66767218200378f Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:25:07 -0800 Subject: [PATCH 093/255] Update outdated test package references (#26368) --- .../BenchmarkDotNet.Extensions.csproj | 4 ++-- test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj index 74a4cf9e089..d67f17c60f9 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index 9883c8b2d63..3274f04acec 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -9,7 +9,7 @@ - - + + From 1ba59744157269451183598dd5bfc8e7924094c4 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 4 Nov 2025 15:18:16 -0800 Subject: [PATCH 094/255] Add Attack Surface Analyzer Script (#26379) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .gitignore | 3 + tools/AttackSurfaceAnalyzer/README.md | 249 +++++++ .../Run-AttackSurfaceAnalyzer.ps1 | 590 ++++++++++++++++ .../Summarize-AsaResults.ps1 | 636 ++++++++++++++++++ tools/AttackSurfaceAnalyzer/docker/Dockerfile | 134 ++++ 5 files changed, 1612 insertions(+) create mode 100644 tools/AttackSurfaceAnalyzer/README.md create mode 100644 tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 create mode 100644 tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 create mode 100644 tools/AttackSurfaceAnalyzer/docker/Dockerfile diff --git a/.gitignore b/.gitignore index ccadde27182..f115e61e22d 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,9 @@ TestsResults*.xml ParallelXUnitResults.xml xUnitResults.xml +# Attack Surface Analyzer results +asa-results/ + # Resharper settings PowerShell.sln.DotSettings.user *.msp diff --git a/tools/AttackSurfaceAnalyzer/README.md b/tools/AttackSurfaceAnalyzer/README.md new file mode 100644 index 00000000000..f57bb21f8c4 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/README.md @@ -0,0 +1,249 @@ +# Attack Surface Analyzer Testing + +This directory contains tools for running Attack Surface Analyzer (ASA) tests on PowerShell MSI installations using Docker. + +## Overview + +Attack Surface Analyzer is a Microsoft tool that helps analyze changes to a system's attack surface. These scripts allow you to run ASA tests locally in a clean Windows container to analyze what changes when PowerShell is installed. + +## Files + +- **Run-AttackSurfaceAnalyzer.ps1** - PowerShell script to run ASA tests with official MSIs +- **Summarize-AsaResults.ps1** - PowerShell script to analyze and summarize ASA results +- **docker/Dockerfile** - Multi-stage Dockerfile for building a container image with ASA pre-installed +- **README.md** - This documentation file + +## Docker Architecture + +The Docker implementation uses a multi-stage build to optimize the testing and result extraction process: + +### Multi-Stage Build Stages + +1. **asa-runner**: Main execution environment + - Base: `mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022` + - Contains Attack Surface Analyzer CLI tools + - Runs the complete test workflow + - Generates reports in both `C:\work` and `C:\reports` directories + +1. **asa-reports**: Minimal results layer + - Base: `mcr.microsoft.com/windows/nanoserver:ltsc2022` + - Contains only the test reports from the runner stage + - Enables clean extraction of results without container internals + +1. **final**: Default stage (inherits from asa-runner) + - Provides backward compatibility + - Used when no specific build target is specified + +### Benefits + +- **Clean Result Extraction**: Reports are isolated in a dedicated layer +- **Efficient Transfer**: Only test results are copied, not the entire container filesystem +- **Fallback Support**: Script includes fallback to volume-based extraction if needed +- **Minimal Footprint**: Final results layer contains only the necessary output files + +## Prerequisites + +- Windows 10/11 or Windows Server +- Docker Desktop with Windows containers enabled +- PowerShell 5.1 or later +- **An official signed PowerShell MSI file** from a released build + +### MSI Requirements + +**Important:** This tool now requires an official, digitally signed PowerShell MSI from Microsoft releases: + +- **Must be signed** by Microsoft Corporation +- **Must be from an official release** (downloaded from [PowerShell Releases](https://github.com/PowerShell/PowerShell/releases)) +- **Local builds are not supported** - unsigned or development MSIs will be rejected +- The script automatically verifies the digital signature before proceeding + +**Where to get official MSIs:** + +- Download from: https://github.com/PowerShell/PowerShell/releases +- Look for files like: `PowerShell-7.x.x-win-x64.msi` + +## Quick Start + +### Option 1: Using the PowerShell Script (Recommended) + +The script requires an official signed PowerShell MSI file: + +```powershell +# Run ASA test with official MSI (MsiPath is required) +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath "C:\path\to\PowerShell-7.4.0-win-x64.msi" + +# Specify custom output directory for results +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -OutputPath "C:\asa-results" + +# Keep the temporary work directory for debugging +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -KeepWorkDirectory +``` + +The script will: + +1. **Verify MSI signature** - Ensures the MSI is officially signed by Microsoft Corporation +1. Create a temporary work directory +1. Build a custom Docker container from the static Dockerfile +1. Start the Windows container with Attack Surface Analyzer +1. Take a baseline snapshot +1. Install the PowerShell MSI +1. Take a post-installation snapshot +1. Export comparison results +1. Copy results back to your specified output directory + +**Security Note:** The script will reject any MSI that is not digitally signed by Microsoft Corporation to ensure analysis is performed only on official releases. + +### Option 2: Using the Dockerfile + +If you prefer to build and use the container image directly: + +```powershell +# Build the Docker image (Dockerfile is in docker subfolder with clean context) +docker build -f tools\AttackSurfaceAnalyzer\docker\Dockerfile -t powershell-asa-test tools\AttackSurfaceAnalyzer\docker\ + +# Run the container with your MSI (script is built into the container) +docker run --rm --isolation process ` + -v "C:\path\to\msi\directory:C:\work" ` + powershell-asa-test +``` + +## Output Files + +The test will generate output files in the `./asa-results/` directory (or your specified `-OutputPath`): + +- **`asa.sqlite`** - SQLite database with full analysis data (primary result file) +- **`install.log`** - MSI installation log file +- **`*_summary.json.txt`** - Summary of detected changes (if generated) +- **`*_results.json.txt`** - Detailed results in JSON format (if generated) +- **`*.sarif`** - SARIF format results (if generated, can be viewed in VS Code) + +## Analyzing Results + +### Using the Summary Script (Recommended) + +Use the included summary script to get a comprehensive analysis: + +```powershell +# Basic summary of ASA results +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 + +# Detailed analysis with rule breakdowns +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 -ShowDetails + +# Analyze results from a specific location +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 -Path "C:\custom\path\asa-results.json" -ShowDetails +``` + +The summary script provides: + +- **Overall statistics** - Total findings, analysis levels, category breakdowns +- **Rule analysis** - Which security rules were triggered and how often +- **File analysis** - Detailed breakdown of file-related security issues by rule type +- **Category cross-reference** - Shows which rules affect which categories + +### Using VS Code + +The SARIF files can be opened directly in VS Code with the SARIF Viewer extension to see a formatted view of the findings. + +### Using PowerShell + +```powershell +# Read the JSON results directly +$results = Get-Content "asa-results\asa-results.json" | ConvertFrom-Json +$results.Results.FILE_CREATED.Count # Number of files created + +# Query the SQLite database (requires SQLite tools) +# Example: List all file changes +# sqlite3 asa.sqlite "SELECT * FROM file_system WHERE change_type != 'NONE'" +``` + +## Troubleshooting + +### Docker Not Available + +The script automatically handles Docker Desktop installation and startup: + +**If Docker Desktop is installed but not running:** + +- The script will automatically start Docker Desktop for you +- It waits up to 60 seconds for Docker to become available +- You'll be prompted for confirmation (supports `-Confirm` and `-WhatIf`) + +**If Docker Desktop is not installed:** + +- The script will prompt you to install it automatically using winget +- After installation completes, start Docker Desktop and run the script again + +**Manual Installation:** + +1. Install Docker Desktop from https://www.docker.com/products/docker-desktop +1. Ensure Docker is running +1. Switch to Windows containers (right-click Docker tray icon → "Switch to Windows containers") + +### Container Fails to Start + +- Ensure you have enough disk space (containers can be large) +- Check that Windows containers are enabled in Docker settings +- Try pulling the base image manually: `docker pull mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022` + +### MSI Signature Verification Fails + +If you get signature verification errors: + +- **Ensure you're using an official MSI** from [PowerShell Releases](https://github.com/PowerShell/PowerShell/releases) +- **Do not use local builds** - only signed release MSIs are supported +- **Check certificate validity** - very old MSIs may have expired certificates +- **Verify file integrity** - redownload the MSI if it may be corrupted + +### No Results Generated + +- Check the install.log file for MSI installation errors +- Run with `-KeepWorkDirectory` to inspect the temporary work directory +- Verify the MSI file is valid and not corrupted + +## Advanced Usage + +### Parameters + +The `Run-AttackSurfaceAnalyzer.ps1` script supports these parameters: + +- **`-MsiPath`** (Required) - Path to the official signed PowerShell MSI file +- **`-OutputPath`** (Optional) - Directory for results (defaults to `./asa-results`) +- **`-ContainerImage`** (Optional) - Custom container base image +- **`-KeepWorkDirectory`** (Optional) - Keep temp directory for debugging + +Example with custom container image: + +```powershell +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 ` + -MsiPath ".\PowerShell-7.4.0-win-x64.msi" ` + -ContainerImage "mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022" +``` + +### Debugging + +To debug issues, keep the work directory and examine the files: + +```powershell +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -KeepWorkDirectory + +# The script will print the work directory path +# You can then examine: +# - run-asa.ps1 - The script that runs in the container +# - install.log - MSI installation log +# - Any other generated files +``` + +## Integration with CI/CD + +These tools were extracted from the GitHub Actions workflow to allow local testing. If you need to integrate ASA testing back into a CI/CD pipeline, you can: + +1. Use the PowerShell script directly in your pipeline +1. Build and push the Docker image to a registry +1. Use the Dockerfile as a base for custom testing scenarios + +## More Information + +- [Attack Surface Analyzer on GitHub](https://github.com/microsoft/AttackSurfaceAnalyzer) +- [Docker for Windows Documentation](https://docs.docker.com/desktop/windows/) +- [SARIF Documentation](https://sarifweb.azurewebsites.net/) diff --git a/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 b/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 new file mode 100644 index 00000000000..2f7e502bff6 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 @@ -0,0 +1,590 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Run Attack Surface Analyzer test locally using Docker to analyze PowerShell MSI installation. + +.DESCRIPTION + This script runs Attack Surface Analyzer in a clean Windows container to analyze + the attack surface changes when installing PowerShell MSI. It takes a baseline + snapshot, installs the MSI, takes a post-installation snapshot, and exports the + comparison results. + +.PARAMETER MsiPath + Path to the official signed PowerShell MSI file to test. This must be a released, + signed MSI from the official PowerShell releases. + +.PARAMETER OutputPath + Directory where results will be saved. Defaults to './asa-results' subdirectory. + +.PARAMETER ContainerImage + Docker container image to use. Defaults to mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022 + +.PARAMETER KeepWorkDirectory + If specified, keeps the temporary work directory after the test completes. + +.EXAMPLE + .\Run-AttackSurfaceAnalyzer.ps1 -MsiPath "C:\path\to\PowerShell-7.4.0-win-x64.msi" + +.EXAMPLE + .\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -OutputPath "C:\asa-results" + +.NOTES + Requires Docker Desktop with Windows containers enabled. + Requires an official signed PowerShell MSI file from a released build. + + Docker Desktop Handling: + - If Docker Desktop is installed but not running, the script will start it automatically + - If Docker Desktop is not installed, the script will prompt to install it using winget + - Waits up to 60 seconds for Docker to become available after starting + + MSI Requirements: + - The MSI must be digitally signed by Microsoft Corporation + - The MSI must be from an official PowerShell release + - Local builds or unsigned MSIs are not supported + + Supports -WhatIf and -Confirm for Docker installation and startup. +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory)] + [string]$MsiPath, + + [Parameter()] + [string]$OutputPath = (Join-Path $PWD "asa-results"), + + [Parameter()] + [string]$ContainerImage = "mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022", + + [Parameter()] + [switch]$KeepWorkDirectory +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $color = switch ($Level) { + "ERROR" { "Red" } + "WARNING" { "Yellow" } + "SUCCESS" { "Green" } + default { "White" } + } + Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color +} + +function Test-MsiSignature { + param( + [Parameter(Mandatory)] + [string]$MsiPath + ) + + Write-Log "Verifying MSI signature..." -Level INFO + + try { + # Get the digital signature information + $signature = Get-AuthenticodeSignature -FilePath $MsiPath + + if ($signature.Status -ne 'Valid') { + Write-Log "MSI signature is not valid. Status: $($signature.Status)" -Level ERROR + return $false + } + + # Check if signed by Microsoft Corporation + $signerCertificate = $signature.SignerCertificate + if (-not $signerCertificate) { + Write-Log "No signer certificate found" -Level ERROR + return $false + } + + $subject = $signerCertificate.Subject + Write-Log "Certificate subject: $subject" -Level INFO + + # Check for Microsoft Corporation in the subject + if ($subject -notmatch "Microsoft Corporation" -and $subject -notmatch "CN=Microsoft Corporation") { + Write-Log "MSI is not signed by Microsoft Corporation" -Level ERROR + Write-Log "Expected: Microsoft Corporation" -Level ERROR + Write-Log "Found: $subject" -Level ERROR + return $false + } + + # Check certificate validity + $validFrom = $signerCertificate.NotBefore + $validTo = $signerCertificate.NotAfter + $now = Get-Date + + if ($now -lt $validFrom -or $now -gt $validTo) { + Write-Log "Certificate is not valid for current date" -Level ERROR + Write-Log "Valid from: $validFrom to: $validTo" -Level ERROR + return $false + } + + Write-Log "MSI signature verification passed" -Level SUCCESS + Write-Log "Signed by: $($signerCertificate.Subject)" -Level SUCCESS + Write-Log "Valid from: $validFrom to: $validTo" -Level SUCCESS + + return $true + } + catch { + Write-Log "Error verifying MSI signature: $_" -Level ERROR + return $false + } +} + +function Test-DockerAvailable { + try { + $null = docker version 2>&1 + return $true + } + catch { + return $false + } +} + +function Test-DockerDesktopInstalled { + # Check if Docker Desktop executable exists + $dockerDesktopPaths = @( + "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe", + "${env:ProgramFiles(x86)}\Docker\Docker\Docker Desktop.exe", + "${env:LOCALAPPDATA}\Programs\Docker\Docker Desktop.exe" + ) + + foreach ($path in $dockerDesktopPaths) { + if (Test-Path $path) { + return $path + } + } + return $null +} + +function Test-DockerDesktopRunning { + $process = Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue + return $null -ne $process +} + +function Start-DockerDesktopApp { + [CmdletBinding(SupportsShouldProcess)] + param() + + $dockerDesktopPath = Test-DockerDesktopInstalled + + if (-not $dockerDesktopPath) { + Write-Log "Docker Desktop executable not found." -Level ERROR + return $false + } + + if (Test-DockerDesktopRunning) { + Write-Log "Docker Desktop is already running." -Level SUCCESS + return $true + } + + if ($PSCmdlet.ShouldProcess("Docker Desktop", "Start application")) { + Write-Log "Starting Docker Desktop..." -Level SUCCESS + Write-Log "This may take a minute for Docker to fully start..." + + try { + Start-Process -FilePath $dockerDesktopPath -WindowStyle Hidden + + # Wait for Docker to become available (up to 60 seconds) + $maxWaitSeconds = 60 + $waitedSeconds = 0 + + while ($waitedSeconds -lt $maxWaitSeconds) { + Start-Sleep -Seconds 5 + $waitedSeconds += 5 + Write-Log "Waiting for Docker to start... ($waitedSeconds/$maxWaitSeconds seconds)" + + if (Test-DockerAvailable) { + Write-Log "Docker Desktop started successfully!" -Level SUCCESS + return $true + } + } + + Write-Log "Docker Desktop was started but is not responding yet. Please wait a moment and try again." -Level WARNING + return $false + } + catch { + Write-Log "Error starting Docker Desktop: $_" -Level ERROR + return $false + } + } + else { + Write-Log "Starting Docker Desktop cancelled by user." -Level WARNING + return $false + } +} + +function Test-WingetAvailable { + try { + $null = Get-Command winget -ErrorAction Stop + return $true + } + catch { + return $false + } +} + +function Install-DockerDesktop { + [CmdletBinding(SupportsShouldProcess, ConfirmImpact="High")] + param() + + if (-not (Test-WingetAvailable)) { + Write-Log "winget is not available. Please install winget (App Installer from Microsoft Store) or install Docker Desktop manually from https://www.docker.com/products/docker-desktop" -Level ERROR + return $false + } + + if ($PSCmdlet.ShouldProcess("Docker Desktop", "Install using winget")) { + Write-Log "Installing Docker Desktop using winget..." -Level SUCCESS + Write-Log "This may take several minutes..." + + try { + winget install docker.dockerdesktop --accept-package-agreements --accept-source-agreements + + if ($LASTEXITCODE -eq 0) { + Write-Log "Docker Desktop installed successfully!" -Level SUCCESS + Write-Log "Please restart Docker Desktop and ensure Windows containers are enabled, then run this script again." -Level SUCCESS + return $true + } + else { + Write-Log "Docker Desktop installation failed with exit code: $LASTEXITCODE" -Level ERROR + return $false + } + } + catch { + Write-Log "Error installing Docker Desktop: $_" -Level ERROR + return $false + } + } + else { + Write-Log "Docker Desktop installation cancelled by user." -Level WARNING + return $false + } +} + +# Verify Docker is available +Write-Log "Checking Docker availability..." +if (-not (Test-DockerAvailable)) { + Write-Log "Docker is not responding." -Level WARNING + + # Check if Docker Desktop is installed but not running + if (Test-DockerDesktopInstalled) { + Write-Log "Docker Desktop is installed but not running." -Level WARNING + + if (Start-DockerDesktopApp) { + Write-Log "Docker Desktop is now running and ready." -Level SUCCESS + } + else { + Write-Log "Failed to start Docker Desktop or it's taking longer than expected." -Level ERROR + Write-Log "Please start Docker Desktop manually and ensure Windows containers are enabled, then run this script again." -Level ERROR + exit 1 + } + } + else { + # Docker Desktop is not installed + Write-Log "Docker Desktop is not installed." -Level WARNING + Write-Log "Docker Desktop is required to run Attack Surface Analyzer tests in containers." -Level WARNING + + if (Install-DockerDesktop) { + Write-Log "Docker Desktop has been installed. Please restart Docker Desktop and run this script again." -Level SUCCESS + exit 0 + } + else { + Write-Log "Please install Docker Desktop manually from https://www.docker.com/products/docker-desktop and ensure it's running with Windows containers enabled." -Level ERROR + exit 1 + } + } +} + +# Verify MSI exists and is properly signed +if (-not (Test-Path $MsiPath)) { + Write-Log "MSI file not found: $MsiPath" -Level ERROR + exit 1 +} + +$MsiPath = Resolve-Path $MsiPath +Write-Log "Using MSI: $MsiPath" + +# Verify MSI signature +if (-not (Test-MsiSignature -MsiPath $MsiPath)) { + Write-Log "MSI signature verification failed. Only official signed PowerShell MSIs are supported." -Level ERROR + Write-Log "Please download an official PowerShell MSI from: https://github.com/PowerShell/PowerShell/releases" -Level ERROR + exit 1 +} + +# Create output directory +$OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) +if (-not (Test-Path $OutputPath)) { + New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null + Write-Log "Created output directory: $OutputPath" +} + +# Create container work directory +$containerWorkDir = Join-Path $env:TEMP "asa-container-work-$(Get-Date -Format 'yyyyMMdd-HHmmss')" +New-Item -ItemType Directory -Force -Path $containerWorkDir | Out-Null +Write-Log "Created container work directory: $containerWorkDir" + +try { + # Use the static Dockerfile from the docker subfolder + $dockerContextPath = Join-Path $PSScriptRoot "docker" + + # Copy MSI to Docker build context + $msiFileName = Split-Path $MsiPath -Leaf + $destMsiPath = Join-Path $dockerContextPath $msiFileName + Write-Log "Copying MSI to Docker build context..." + Copy-Item $MsiPath -Destination $destMsiPath + $staticDockerfilePath = Join-Path $dockerContextPath "Dockerfile" + Write-Log "Using static Dockerfile: $staticDockerfilePath" + + if (-not (Test-Path $staticDockerfilePath)) { + Write-Log "Static Dockerfile not found at: $staticDockerfilePath" -Level ERROR + exit 1 + } + + Write-Log "Docker build context: $dockerContextPath" + + # Build custom container image from static Dockerfile + Write-Log "=========================================" -Level SUCCESS + Write-Log "Building custom Attack Surface Analyzer container..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + Write-Log "=========================================" -Level SUCCESS + Write-Log "Building ASA test container..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + Write-Log "This may take several minutes..." + + # Build the asa-reports stage specifically + $reportsImageName = "powershell-asa-reports:latest" + docker build --target asa-reports -t $reportsImageName -f $staticDockerfilePath $dockerContextPath + + if ($LASTEXITCODE -ne 0) { + Write-Log "Docker build failed with exit code: $LASTEXITCODE" -Level ERROR + exit 1 + } + + Write-Log "Build completed successfully" -Level SUCCESS + + # Extract reports from the built image + Write-Log "=========================================" -Level SUCCESS + Write-Log "Extracting reports to: $OutputPath" -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + $tempContainerName = "asa-reports-extract-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + + try { + # Create a container from the reports image (but don't run it) + docker create --name $tempContainerName $reportsImageName + + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to create temporary container for extraction" -Level ERROR + exit 1 + } + + # Try to extract known report file patterns individually + Write-Log "Extracting report files..." -Level INFO + + # Extract standardized report files directly (no file listing needed) + # Extract files with standardized names (no wildcards needed) + Write-Log "Extracting standardized report files..." -Level INFO + $reportFilePatterns = @( + "asa.sqlite", + "asa-results.json", + "install.log" + ) + + $extractedAny = $false + + foreach ($filename in $reportFilePatterns) { + try { + Write-Log "Trying to extract file: $filename" -Level INFO + docker cp "${tempContainerName}:/$filename" $OutputPath 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Log "Successfully extracted: $filename" -Level SUCCESS + $extractedAny = $true + } else { + Write-Log "File not found: $filename" -Level INFO + } + } + catch { + Write-Log "Error extracting file $filename : $_" -Level WARNING + } + } + + # Alternative approach: extract the entire reports directory if individual files don't work + if (-not $extractedAny) { + Write-Log "Trying to extract entire directory..." -Level INFO + docker cp "${tempContainerName}:/" "$OutputPath/reports" 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Log "Successfully extracted reports directory" -Level SUCCESS + $extractedAny = $true + } + } + + if ($extractedAny) { + Write-Log "Report extraction completed successfully" -Level SUCCESS + } else { + Write-Log "No reports could be extracted - this may be normal if no issues were found" -Level WARNING + } + } + finally { + # Clean up the temporary container + docker rm $tempContainerName -f 2>$null + } + + # Check what files were extracted + Write-Host "" + Write-Log "=========================================" -Level SUCCESS + Write-Log "Checking extracted results..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + $resultFiles = Get-ChildItem -Path $OutputPath -ErrorAction SilentlyContinue + $copiedCount = $resultFiles.Count + + if ($copiedCount -eq 0) { + Write-Log "Warning: No result files found in extracted output" -Level WARNING + } + else { + Write-Log "Successfully extracted $copiedCount file(s):" -Level SUCCESS + $resultFiles | ForEach-Object { + if ($_.PSIsContainer) { + Write-Log " - $($_.Name) (directory)" -Level SUCCESS + } else { + Write-Log " - $($_.Name) ($([math]::Round($_.Length/1KB, 2)) KB)" -Level SUCCESS + } + } + } + + Write-Host "" + Write-Log "=========================================" -Level SUCCESS + Write-Log "Attack Surface Analyzer test completed!" -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + Write-Log "Results saved to: $OutputPath" -Level SUCCESS + + # Check for ASA GUI availability and launch interactive analysis + $dbPath = Join-Path $OutputPath "asa.sqlite" + $jsonPath = Join-Path $OutputPath "asa-results.json" + + if (Test-Path $dbPath) { + # Check if ASA CLI is available + $asaAvailable = $false + try { + $asaVersion = asa --version 2>$null + if ($LASTEXITCODE -eq 0) { + $asaAvailable = $true + Write-Log "Attack Surface Analyzer CLI detected: $($asaVersion.Trim())" -Level INFO + } + } + catch { + # ASA not available via PATH + } + + # Try dotnet tool global path if ASA not found in PATH + if (-not $asaAvailable) { + $globalToolsPath = "$env:USERPROFILE\.dotnet\tools\asa.exe" + if (Test-Path $globalToolsPath) { + try { + $asaVersion = & $globalToolsPath --version 2>$null + if ($LASTEXITCODE -eq 0) { + $asaAvailable = $true + Write-Log "Attack Surface Analyzer found in global tools: $($asaVersion.Trim())" -Level INFO + # Use full path for subsequent commands + $asaCommand = $globalToolsPath + } + } + catch { + # Global tools ASA not working + } + } + } else { + $asaCommand = "asa" + } + + if ($asaAvailable) { + Write-Log "Launching Attack Surface Analyzer GUI for interactive analysis..." -Level SUCCESS + try { + # Launch ASA GUI with the database file + $asaProcess = Start-Process -FilePath $asaCommand -ArgumentList "gui", "--databasefilename", "`"$dbPath`"" -PassThru -NoNewWindow:$false + + if ($asaProcess) { + Write-Log "ASA GUI launched successfully (PID: $($asaProcess.Id))" -Level SUCCESS + Write-Log "Interactive analysis interface is now available" -Level INFO + } else { + Write-Log "Failed to launch ASA GUI" -Level WARNING + } + } + catch { + Write-Log "Error launching ASA GUI: $_" -Level WARNING + Write-Log "You can manually launch the GUI with: asa gui --databasefilename `"$dbPath`"" -Level INFO + } + } else { + Write-Log "Attack Surface Analyzer CLI not found" -Level INFO + Write-Log "Install ASA globally to enable GUI analysis: dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI" -Level INFO + Write-Log "Then launch GUI manually with: asa gui --databasefilename `"$dbPath`"" -Level INFO + } + } else { + Write-Log "Database file not found - cannot launch ASA GUI" -Level WARNING + } + + # Also check for VS Code integration for JSON analysis + if (Test-Path $jsonPath) { + # Detect if running in VS Code + $isVSCode = $false + + if ($env:VSCODE_PID -or $env:TERM_PROGRAM -eq "vscode" -or $env:VSCODE_INJECTION -eq "1") { + $isVSCode = $true + } + + # Check if 'code' command is available + if (-not $isVSCode) { + try { + $null = & code --version 2>$null + if ($LASTEXITCODE -eq 0) { + $isVSCode = $true + } + } + catch { + # 'code' command not available + } + } + + if ($isVSCode) { + Write-Log "VS Code detected - opening JSON results for analysis..." -Level INFO + try { + & code $jsonPath + if ($LASTEXITCODE -eq 0) { + Write-Log "JSON results file opened in VS Code: $jsonPath" -Level SUCCESS + } else { + Write-Log "Failed to open JSON file in VS Code" -Level WARNING + } + } + catch { + Write-Log "Error opening JSON file in VS Code: $_" -Level WARNING + } + } else { + Write-Log "JSON analysis results available at: $jsonPath" -Level INFO + Write-Log "Open this file in VS Code or any JSON viewer for detailed analysis" -Level INFO + } + } +} +finally { + # Cleanup + if (-not $KeepWorkDirectory) { + Write-Log "Cleaning up temporary work directory..." + Remove-Item -Path $containerWorkDir -Recurse -Force -ErrorAction SilentlyContinue + Write-Log "Cleanup completed" + } + else { + Write-Log "Work directory preserved at: $containerWorkDir" -Level SUCCESS + } + + # Always cleanup MSI file from Docker build context + if ($destMsiPath -and (Test-Path $destMsiPath)) { + Write-Log "Cleaning up MSI file from Docker context..." + Remove-Item -Path $destMsiPath -Force -ErrorAction SilentlyContinue + } +} diff --git a/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 b/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 new file mode 100644 index 00000000000..00f27014037 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 @@ -0,0 +1,636 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 5.1 +<# +.SYNOPSIS + Summarizes Attack Surface Analyzer (ASA) results from a JSON file. + +.DESCRIPTION + This script analyzes ASA JSON results and provides a comprehensive summary of security findings, + including counts by category, analysis levels, and detailed breakdowns of security issues. + +.PARAMETER Path + Path to the ASA results JSON file. Defaults to 'asa-results\asa-results.json' in the current directory. + +.PARAMETER ShowDetails + Shows detailed information about each finding category. + +.PARAMETER IncludeInformationalEvent + Includes informational events in the analysis. By default, only WARNING and ERROR events are processed. + +.PARAMETER IncludeDebugEvent + Includes debug events in the analysis. By default, only WARNING and ERROR events are processed. + +.EXAMPLE + .\Summarize-AsaResults.ps1 + + Summarizes the ASA results with basic statistics, showing only WARNING and ERROR events. + +.EXAMPLE + .\Summarize-AsaResults.ps1 -ShowDetails + + Shows detailed breakdown of findings by category, filtering out informational and debug events. + +.EXAMPLE + .\Summarize-AsaResults.ps1 -IncludeInformationalEvent + + Includes informational events along with WARNING and ERROR events in the analysis..NOTES + Author: GitHub Copilot + Version: 1.0 + Created for PowerShell ASA Analysis +#> + +[CmdletBinding()] +param( + [Parameter()] + [string]$Path = "asa-results\asa-results.json", + + [Parameter()] + [switch]$ShowDetails, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent +) + +function Get-AsaSummary { + param( + [Parameter(Mandatory)] + $AsaData, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent + ) + + # Extract metadata + $metadata = $AsaData["Metadata"] + $results = $AsaData["Results"] + + # Initialize counters + $summary = @{ + Metadata = @{ + Version = $metadata["compare-version"] + OS = $metadata["compare-os"] + OSVersion = $metadata["compare-osversion"] + BaseRunId = "" + CompareRunId = "" + } + Categories = @{} + TotalFindings = 0 + AnalysisLevels = @{ + WARNING = 0 + ERROR = 0 + INFORMATION = 0 + DEBUG = 0 + } + RuleTypes = @{} + FileIssuesByRule = @{} + FileExtensionSummary = @{} + TimeSpan = $null + } + + # Process each category + foreach ($categoryName in $results.Keys) { + $categoryData = $results[$categoryName] + + $summary.Categories[$categoryName] = @{ + Count = 0 + Items = @() + } + + # Process items in category with filtering + foreach ($item in $categoryData) { + # Filter events based on analysis level + $analysisLevel = $item["Analysis"] + if ($analysisLevel) { + # Skip informational events unless explicitly included + if ($analysisLevel -eq "INFORMATION" -and -not $IncludeInformationalEvent) { + continue + } + # Skip debug events unless explicitly included + if ($analysisLevel -eq "DEBUG" -and -not $IncludeDebugEvent) { + continue + } + + $summary.AnalysisLevels[$analysisLevel]++ + } # If we reach here, the item passed the filter + $summary.Categories[$categoryName].Count++ + $summary.TotalFindings++ + + # Extract run IDs and calculate timespan + if ($item["BaseRunId"]) { + $summary.Metadata.BaseRunId = $item["BaseRunId"] + } + if ($item["CompareRunId"]) { + $summary.Metadata.CompareRunId = $item["CompareRunId"] + } + + # Process rules + foreach ($rule in $item["Rules"]) { + $ruleName = $rule["Name"] + if (-not $summary.RuleTypes.ContainsKey($ruleName)) { + $summary.RuleTypes[$ruleName] = @{ + Count = 0 + Description = $rule["Description"] + Flag = $rule["Flag"] + Platforms = $rule["Platforms"] + Categories = @{} + } + } + $summary.RuleTypes[$ruleName].Count++ + + # Track which categories this rule appears in + if (-not $summary.RuleTypes[$ruleName].Categories.ContainsKey($categoryName)) { + $summary.RuleTypes[$ruleName].Categories[$categoryName] = 0 + } + $summary.RuleTypes[$ruleName].Categories[$categoryName]++ + + # For file-related categories, track file extension if available + if ($categoryName -like "*FILE*" -and $item["Identity"]) { + $fileExtension = [System.IO.Path]::GetExtension($item["Identity"]).ToLower() + if (-not $fileExtension) { $fileExtension = "(no extension)" } + + # Track by rule and extension + if (-not $summary.FileIssuesByRule.ContainsKey($ruleName)) { + $summary.FileIssuesByRule[$ruleName] = @{} + } + if (-not $summary.FileIssuesByRule[$ruleName].ContainsKey($fileExtension)) { + $summary.FileIssuesByRule[$ruleName][$fileExtension] = 0 + } + $summary.FileIssuesByRule[$ruleName][$fileExtension]++ + + # Track overall file extension summary + if (-not $summary.FileExtensionSummary.ContainsKey($fileExtension)) { + $summary.FileExtensionSummary[$fileExtension] = @{ + Count = 0 + Rules = @{} + Categories = @{} + } + } + $summary.FileExtensionSummary[$fileExtension].Count++ + + # Track which rules affect this extension + if (-not $summary.FileExtensionSummary[$fileExtension].Rules.ContainsKey($ruleName)) { + $summary.FileExtensionSummary[$fileExtension].Rules[$ruleName] = 0 + } + $summary.FileExtensionSummary[$fileExtension].Rules[$ruleName]++ + + # Track which categories this extension appears in + if (-not $summary.FileExtensionSummary[$fileExtension].Categories.ContainsKey($categoryName)) { + $summary.FileExtensionSummary[$fileExtension].Categories[$categoryName] = 0 + } + $summary.FileExtensionSummary[$fileExtension].Categories[$categoryName]++ + } + } + + # Store item details for detailed view + $summary.Categories[$categoryName].Items += @{ + Identity = $item["Identity"] + Analysis = $item["Analysis"] + Rules = $item["Rules"] + Compare = $item["Compare"] + } + } + } + + # Calculate timespan if we have both run IDs + if ($summary.Metadata.BaseRunId -and $summary.Metadata.CompareRunId) { + try { + $baseTime = [DateTime]::Parse($summary.Metadata.BaseRunId) + $compareTime = [DateTime]::Parse($summary.Metadata.CompareRunId) + $summary.TimeSpan = $compareTime - $baseTime + } + catch { + $summary.TimeSpan = "Unable to calculate" + } + } + + return $summary +} + +function Write-ConsoleSummary { + param( + [Parameter(Mandatory)] + [hashtable]$Summary, + + [Parameter()] + [switch]$ShowDetails, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent + ) + + # Header + Write-Host ("=" * 80) -ForegroundColor Cyan + Write-Host "Attack Surface Analyzer Results Summary" -ForegroundColor Cyan + Write-Host ("=" * 80) -ForegroundColor Cyan + Write-Host "" + + # Metadata + Write-Host "Analysis Metadata:" -ForegroundColor Yellow + Write-Host " ASA Version: $($Summary.Metadata.Version)" -ForegroundColor White + Write-Host " Operating System: $($Summary.Metadata.OS) ($($Summary.Metadata.OSVersion))" -ForegroundColor White + if ($Summary.TimeSpan -and $Summary.TimeSpan -ne "Unable to calculate") { + Write-Host " Analysis Duration: $($Summary.TimeSpan.ToString())" -ForegroundColor White + } + Write-Host "" + + # Overall Statistics + Write-Host "Overall Statistics:" -ForegroundColor Yellow + Write-Host " Total Findings: $($Summary.TotalFindings)" -ForegroundColor White + + # Show filtering information + $filterInfo = @() + if (-not $IncludeInformationalEvent) { $filterInfo += "INFORMATION events excluded" } + if (-not $IncludeDebugEvent) { $filterInfo += "DEBUG events excluded" } + if ($filterInfo.Count -gt 0) { + Write-Host " Filtering: $($filterInfo -join ', ')" -ForegroundColor DarkYellow + } + + # Analysis Levels + Write-Host " Analysis Levels:" -ForegroundColor White + foreach ($level in $Summary.AnalysisLevels.Keys | Sort-Object) { + $count = $Summary.AnalysisLevels[$level] + $color = switch ($level) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + Write-Host " $level`: $count" -ForegroundColor $color + } + Write-Host "" + + # Category Breakdown + Write-Host "Findings by Category:" -ForegroundColor Yellow + $sortedCategories = $Summary.Categories.GetEnumerator() | Sort-Object { $_.Value.Count } -Descending + + foreach ($category in $sortedCategories) { + $categoryName = $category.Key + $count = $category.Value.Count + + if ($count -gt 0) { + Write-Host " $categoryName`: $count items" -ForegroundColor Cyan + } + else { + Write-Host " $categoryName`: $count items" -ForegroundColor DarkGray + } + } + Write-Host "" + + # Rule Types Summary + Write-Host "Top Security Rules Triggered:" -ForegroundColor Yellow + $topRules = $Summary.RuleTypes.GetEnumerator() | + Sort-Object { $_.Value.Count } -Descending | + Select-Object -First 10 + + foreach ($rule in $topRules) { + $ruleName = $rule.Key + $count = $rule.Value.Count + $flag = $rule.Value.Flag + + $color = switch ($flag) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " [$flag] $ruleName`: $count occurrences" -ForegroundColor $color + if ($ShowDetails) { + Write-Host " Description: $($rule.Value.Description)" -ForegroundColor DarkGray + Write-Host " Platforms: $($rule.Value.Platforms -join ', ')" -ForegroundColor DarkGray + + # Show breakdown by category for this rule + if ($rule.Value.Categories.Count -gt 0) { + Write-Host " Categories:" -ForegroundColor DarkGray + foreach ($cat in $rule.Value.Categories.GetEnumerator() | Sort-Object { $_.Value } -Descending) { + Write-Host " $($cat.Key): $($cat.Value) occurrences" -ForegroundColor Gray + } + } + } + } + + # File Extension Summary + if ($Summary.FileExtensionSummary.Count -gt 0) { + Write-Host "" + Write-Host "File Extension Analysis:" -ForegroundColor Yellow + + $sortedExtensions = $Summary.FileExtensionSummary.GetEnumerator() | + Sort-Object { $_.Value.Count } -Descending | + Select-Object -First 15 + + foreach ($extEntry in $sortedExtensions) { + $extension = $extEntry.Key + $count = $extEntry.Value.Count + $displayExt = if ($extension -eq "(no extension)") { $extension } else { "*$extension" } + + Write-Host " $displayExt`: $count files" -ForegroundColor Cyan + + if ($ShowDetails) { + # Show top rules for this extension + $topRulesForExt = $extEntry.Value.Rules.GetEnumerator() | + Sort-Object { $_.Value } -Descending | + Select-Object -First 3 + + foreach ($ruleEntry in $topRulesForExt) { + $ruleName = $ruleEntry.Key + $ruleCount = $ruleEntry.Value + Write-Host " $ruleName`: $ruleCount files" -ForegroundColor Gray + } + } + } + } + + # Detailed Rule Analysis by Category + if ($ShowDetails) { + Write-Host "" + Write-Host "Detailed Rule Analysis by Category:" -ForegroundColor Yellow + + # Focus on file-related categories + $fileCategories = $Summary.Categories.GetEnumerator() | Where-Object { $_.Key -like "*FILE*" -and $_.Value.Count -gt 0 } + + foreach ($category in $fileCategories) { + $categoryName = $category.Key + Write-Host "" + Write-Host " $categoryName Rules Breakdown:" -ForegroundColor Cyan + + # Get rules that appear in this category + $categoryRules = $Summary.RuleTypes.GetEnumerator() | + Where-Object { $_.Value.Categories.ContainsKey($categoryName) } | + Sort-Object { $_.Value.Categories[$categoryName] } -Descending + + foreach ($ruleEntry in $categoryRules) { + $ruleName = $ruleEntry.Key + $count = $ruleEntry.Value.Categories[$categoryName] + $flag = $ruleEntry.Value.Flag + + $color = switch ($flag) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " [$flag] $ruleName`: $count files" -ForegroundColor $color + } + } + + # Show file extension breakdown if available + if ($Summary.FileIssuesByRule.Count -gt 0) { + Write-Host "" + Write-Host "File Issues by Rule and Extension:" -ForegroundColor Yellow + + foreach ($ruleEntry in $Summary.FileIssuesByRule.GetEnumerator()) { + $ruleName = $ruleEntry.Key + Write-Host "" + Write-Host " $ruleName`:" -ForegroundColor Cyan + + $sortedExtensions = $ruleEntry.Value.GetEnumerator() | Sort-Object { $_.Value } -Descending + foreach ($extEntry in $sortedExtensions) { + $extension = $extEntry.Key + $count = $extEntry.Value + Write-Host " $extension`: $count files" -ForegroundColor White + } + } + } + } + + # Detailed Category Information + if ($ShowDetails) { + Write-Host "" + Write-Host "Detailed Category Breakdown:" -ForegroundColor Yellow + + foreach ($category in $sortedCategories | Where-Object { $_.Value.Count -gt 0 }) { + $categoryName = $category.Key + $items = $category.Value.Items + + Write-Host "" + Write-Host " $categoryName ($($items.Count) items):" -ForegroundColor Cyan + + # Group by analysis level + $groupedByAnalysis = $items | Group-Object Analysis + foreach ($group in $groupedByAnalysis) { + $level = $group.Name + $count = $group.Count + + $color = switch ($level) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " $level`: $count items" -ForegroundColor $color + } + + # Show individual file details for file-related categories + if ($categoryName -like "*FILE*" -and $items.Count -gt 0) { + # Check if this category contains files with expired signatures + $expiredSigItems = $items | Where-Object { + $_.Rules -and ($_.Rules | Where-Object { $_.Name -eq 'Binaries with expired signatures' }) + } + + if ($expiredSigItems.Count -gt 0) { + Write-Host "" + Write-Host " Files with Expired Signatures (grouped by Issuer):" -ForegroundColor DarkCyan + + # Group by issuer only + $groupedByIssuer = @{} + foreach ($item in $expiredSigItems) { + if ($item.Compare -and $item.Compare.SignatureStatus -and $item.Compare.SignatureStatus.SigningCertificate) { + $cert = $item.Compare.SignatureStatus.SigningCertificate + $issuer = $cert.Issuer + $notAfter = $cert.NotAfter + $identity = $item.Identity + + if (-not $groupedByIssuer.ContainsKey($issuer)) { + $groupedByIssuer[$issuer] = @() + } + $groupedByIssuer[$issuer] += [PSCustomObject]@{ + Identity = $identity + NotAfter = $notAfter + } + } + } + + # Display grouped results + $sortedIssuers = $groupedByIssuer.GetEnumerator() | Sort-Object Name + + foreach ($issuerGroup in $sortedIssuers) { + $issuer = $issuerGroup.Name + $files = $issuerGroup.Value + $fileCount = $files.Count + + Write-Host "" + Write-Host " Issuer: $issuer" -ForegroundColor Yellow + Write-Host " Files ($fileCount):" -ForegroundColor White + + # Sort files by full file path + $sortedFiles = $files | Sort-Object Identity + + # Show all files + foreach ($file in $sortedFiles) { + # Get identity - handle both hashtable and PSCustomObject + $filePath = if ($file -is [hashtable]) { $file['Identity'] } else { $file.Identity } + + # Format date without time + $expirationDate = 'Unknown' + $notAfterValue = if ($file -is [hashtable]) { $file['NotAfter'] } else { $file.NotAfter } + if ($notAfterValue) { + try { + $expirationDate = ([DateTime]::Parse($notAfterValue)).ToString('yyyy-MM-dd') + } + catch { + $expirationDate = 'Unknown' + } + } + Write-Host " [Expired: $expirationDate] $filePath" -ForegroundColor Gray + } + } + + # Show other files (non-expired signature issues) + $otherFiles = $items | Where-Object { + -not ($_.Rules -and ($_.Rules | Where-Object { $_.Name -eq 'Binaries with expired signatures' })) + } + + if ($otherFiles.Count -gt 0) { + Write-Host "" + Write-Host " Other Files:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(20, $otherFiles.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $otherFiles[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + if ($item.Rules -and $item.Rules.Count -gt 0) { + $ruleNames = $item.Rules | ForEach-Object { $_.Name } + Write-Host " [$analysis] $identity" -ForegroundColor $color + Write-Host " Rules: $($ruleNames -join ', ')" -ForegroundColor DarkGray + } + else { + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + } + + if ($otherFiles.Count -gt $displayLimit) { + Write-Host " ... and $($otherFiles.Count - $displayLimit) more files" -ForegroundColor DarkGray + } + } + } + else { + # No expired signatures, show standard file listing + Write-Host "" + Write-Host " Files:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(50, $items.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $items[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + # Show triggered rules for this file + if ($item.Rules -and $item.Rules.Count -gt 0) { + $ruleNames = $item.Rules | ForEach-Object { $_.Name } + Write-Host " [$analysis] $identity" -ForegroundColor $color + Write-Host " Rules: $($ruleNames -join ', ')" -ForegroundColor DarkGray + } + else { + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + } + + if ($items.Count -gt $displayLimit) { + Write-Host " ... and $($items.Count - $displayLimit) more files" -ForegroundColor DarkGray + } + } + } + # Show details for non-file categories (users, groups, etc.) + elseif ($items.Count -gt 0) { + Write-Host "" + Write-Host " Items:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(20, $items.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $items[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + + if ($items.Count -gt $displayLimit) { + Write-Host " ... and $($items.Count - $displayLimit) more items" -ForegroundColor DarkGray + } + } + } + } + + Write-Host "" + Write-Host ("=" * 80) -ForegroundColor Cyan +} + +# Main execution +try { + # Validate input file + if (-not (Test-Path $Path)) { + Write-Error "ASA results file not found: $Path" + exit 1 + } + + Write-Verbose "Reading ASA results from: $Path" + + # Load and parse JSON + $jsonContent = Get-Content -Path $Path -Raw -Encoding UTF8 + $asaData = $jsonContent | ConvertFrom-Json -AsHashtable + + # Generate summary + Write-Verbose "Analyzing ASA results..." + $summary = Get-AsaSummary -AsaData $asaData -IncludeInformationalEvent:$IncludeInformationalEvent -IncludeDebugEvent:$IncludeDebugEvent + + # Output results to console + Write-ConsoleSummary -Summary $summary -ShowDetails:$ShowDetails -IncludeInformationalEvent:$IncludeInformationalEvent -IncludeDebugEvent:$IncludeDebugEvent +} +catch { + Write-Error "Error processing ASA results: $($_.Exception.Message)" + Write-Error $_.ScriptStackTrace + exit 1 +} diff --git a/tools/AttackSurfaceAnalyzer/docker/Dockerfile b/tools/AttackSurfaceAnalyzer/docker/Dockerfile new file mode 100644 index 00000000000..3e4aaa3b717 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/docker/Dockerfile @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Multi-stage Dockerfile for Attack Surface Analyzer Testing +# Stage 1: Build and run ASA tests +# Stage 2: Extract reports to scratch layer + +# Stage 1: Test execution environment +FROM mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022@sha256:28f3a59216a7f91dfc4730ea47e236e2ffbb519975725bf8231f57e69dab3ca8 AS asa-runner + +# Set shell to PowerShell for easier scripting +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +# Install Attack Surface Analyzer as a global .NET tool +RUN dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI --version 2.3.328 + +# Add .NET tools directory to PATH +RUN $env:PATH += ';C:/Users/ContainerAdministrator/.dotnet/tools'; \ + [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) + +# Set working directory and create reports directory +WORKDIR C:/work +RUN New-Item -ItemType Directory -Path C:\reports -Force | Out-Null + +# Take baseline snapshot before installation +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Taking baseline snapshot..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa collect -f -r -u -l --directories 'C:\Program Files\PowerShell,C:\Program Files (x86)\PowerShell' --runid before; \ + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to take baseline snapshot"; exit 1 } + +# Copy the PowerShell MSI file from build context +COPY *.msi ./powershell.msi + +# Install PowerShell MSI +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Installing PowerShell MSI..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + Write-Host "MSI file: C:\work\powershell.msi"; \ + $argumentList = '/i C:\work\powershell.msi /quiet /norestart /l*vx C:\work\install.log ADD_PATH=1'; \ + Write-Host "Running: msiexec $argumentList"; \ + $msiProcess = Start-Process msiexec.exe -ArgumentList $argumentList -Wait -NoNewWindow -PassThru; \ + if ($msiProcess.ExitCode -ne 0) { \ + Write-Host "MSI installation failed with exit code: $($msiProcess.ExitCode)"; \ + throw "MSI installation failed. Check install.log for details" \ + } + +# Take post-installation snapshot +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Taking post-installation snapshot..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa collect -f -r -u -l --directories 'C:\Program Files\PowerShell,C:\Program Files (x86)\PowerShell' --runid after; \ + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to take post-installation snapshot"; exit 1 } + +# Export comparison results +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Exporting comparison results..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa export-collect --savetodatabase --resultlevels WARNING,ERROR,FATAL --firstrunid before --secondrunid after; \ + if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to export results with exit code: $LASTEXITCODE" } + +# Export comparison results +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Exporting comparison results..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa export-collect --readfromsavedcomparisons; \ + if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to export results with exit code: $LASTEXITCODE" } + + +# Copy and standardize JSON result files +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Processing JSON result files..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + $jsonFiles = Get-ChildItem -Path "*.json.txt" -ErrorAction SilentlyContinue; \ + if ($jsonFiles.Count -eq 0) { \ + Write-Warning 'No JSON.TXT files found - checking for .json files...'; \ + $jsonFiles = Get-ChildItem -Path "*.json" -ErrorAction SilentlyContinue \ + }; \ + if ($jsonFiles.Count -eq 0) { \ + throw 'No JSON files found - ASA may not have generated results' \ + } else { \ + $jsonFiles | ForEach-Object { \ + Write-Host "Found JSON file: $($_.Name)"; \ + if (-not (Test-Path $_.FullName)) { \ + throw "JSON file not accessible: $($_.FullName)" \ + }; \ + Write-Host "Copying to standard name: asa-results.json"; \ + Copy-Item -Path $_.FullName -Destination C:\work\asa-results.json -ErrorAction Stop; \ + Copy-Item -Path $_.FullName -Destination C:\reports\asa-results.json -ErrorAction Stop; \ + } \ + } + +# Copy SQLite database file if it exists +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Copying SQLite database..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + if (Test-Path "asa.sqlite") { \ + Write-Host "Copying: asa.sqlite"; \ + Copy-Item -Path "asa.sqlite" -Destination C:\reports\ -ErrorAction Stop \ + } else { \ + throw 'SQLite database (asa.sqlite) not found - this may indicate ASA export issues' \ + } + +# Copy installation log file +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Copying installation log..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + if (-not (Test-Path "C:\work\install.log")) { \ + throw "Required installation log file not found: C:\work\install.log" \ + }; \ + Copy-Item -Path "C:\work\install.log" -Destination C:\reports\ -ErrorAction Stop; \ + Write-Host "Attack Surface Analyzer test completed!" -ForegroundColor Green + +# Default command shows completion message +CMD Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Container ready. Reports available in C:\reports\" -ForegroundColor Cyan; \ + Write-Host "=========================================" + +# Stage 2: Reports-only layer using minimal Windows base +FROM mcr.microsoft.com/windows/nanoserver:ltsc2022@sha256:307874138e4dc064d0538b58c6f028419ab82fb15fcabaf6d5378ba32c235266 AS asa-reports + +# Set working directory to root +WORKDIR / + +# Copy only the report files from the runner stage to root level +COPY --from=asa-runner C:/reports/ ./ + +# Stage 3: Final stage (defaults to the runner for backward compatibility) +FROM asa-runner AS final + +# Label for documentation +LABEL description="Windows container for running Attack Surface Analyzer tests on PowerShell MSI installations" +LABEL version="1.0" +LABEL maintainer="PowerShell Team" From bf1c8ed827fff88647b33309d22fbb8abed48955 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 5 Nov 2025 09:20:02 -0800 Subject: [PATCH 095/255] Add a backport prompt for copilot (#26383) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../backport-pr-to-release-branch.prompt.md | 536 ++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 .github/prompts/backport-pr-to-release-branch.prompt.md diff --git a/.github/prompts/backport-pr-to-release-branch.prompt.md b/.github/prompts/backport-pr-to-release-branch.prompt.md new file mode 100644 index 00000000000..8117bdc5183 --- /dev/null +++ b/.github/prompts/backport-pr-to-release-branch.prompt.md @@ -0,0 +1,536 @@ +--- +description: Guide for backporting changes to PowerShell release branches +--- + +# Backport a Change to a PowerShell Release Branch + +## 1 — Goal + +Create a backport PR that applies changes from a merged PR to a release branch (e.g., `release/v7.4`, `release/v7.5`). The backport must follow the repository's established format and include proper references to the original PR. + +## 2 — Prerequisites for the model + +- You have full repository access +- You can run git commands +- You can read PR information from the repository +- Ask clarifying questions if the target release branch or original PR number is unclear + +## 3 — Required user inputs + +If the user hasn't specified a PR number, help them find one: + +### Finding PRs that need backporting + +1. Ask the user which release version they want to backport to (e.g., `7.4`, `7.5`) +2. Search for PRs with the appropriate label using GitHub CLI: + +```powershell +$Owner = "PowerShell" +$Repo = "PowerShell" +$version = "7.4" # or user-specified version +$considerLabel = "Backport-$version.x-Consider" + +$prsJson = gh pr list --repo "$Owner/$Repo" --label $considerLabel --state merged --json number,title,url,labels,mergedAt --limit 100 2>&1 +$prs = $prsJson | ConvertFrom-Json +# Sort PRs from oldest merged to newest merged +$prs = $prs | Sort-Object mergedAt +``` + +3. Present the list of PRs to the user with: + - PR number + - PR title + - Merged date + - URL + +4. Ask the user: "Which PR would you like to backport?" (provide the PR number) + +### After selecting a PR + +Once the user selects a PR (or if they provided one initially), confirm: +- **Original PR number**: The PR number that was merged to the main branch (e.g., 26193) +- **Target release**: The release number (e.g., `7.4`, `7.5`, `7.5.1`) + +Example: "Backport PR 26193 to release/v7.4" + +## 4 — Implementation steps (must be completed in order) + +### Step 1: Verify the original PR exists and is merged + +1. Fetch the original PR information using the PR number +2. Confirm the PR state is `MERGED` +3. Extract the following information: + - Merge commit SHA + - Original PR title + - Original PR author + - Original CL label (if present, typically starts with `CL-`) + +If the PR is not merged, stop and inform the user. + +4. Check if backport already exists or has been attempted: + ```powershell + gh pr list --repo PowerShell/PowerShell --search "in:title [release/v7.4] " --state all + ``` + + If a backport PR already exists, inform the user and ask if they want to continue. + +5. Check backport labels to understand status: + - `Backport-7.4.x-Migrated`: Indicates previous backport attempt (may have failed or had issues) + - `Backport-7.4.x-Done`: Already backported successfully + - `Backport-7.4.x-Approved`: Ready for backporting + - `Backport-7.4.x-Consider`: Under consideration for backporting + + If status is "Done", inform the user that backport may already be complete. + +### Step 2: Create the backport branch + +1. Identify the correct remote to fetch from: + ```bash + git remote -v + ``` + + Look for the remote that points to `https://github.com/PowerShell/PowerShell` (typically named `upstream` or `origin`). Use this remote name in subsequent commands. + +2. Ensure you have the latest changes from the target release branch: + ```bash + git fetch + ``` + + Example: `git fetch upstream release/v7.4` + +3. Create a new branch from the target release branch: + ```bash + git checkout -b backport- / + ``` + + Example: `git checkout -b backport-26193 upstream/release/v7.4` + +### Step 3: Cherry-pick the merge commit + +1. Cherry-pick the merge commit from the original PR: + ```bash + git cherry-pick + ``` + +2. If conflicts occur: + - Inform the user about the conflicts + - List the conflicting files + - Fetch the original PR diff to understand the changes: + ```bash + gh pr diff --repo PowerShell/PowerShell | Out-File pr-diff.txt + ``` + - Review the diff to understand what the PR changed + - Figure out why there is a conflict and resolve it + - Create a summary of the conflict resolution: + * Which files had conflicts + * Nature of each conflict (parameter changes, code removal, etc.) + * How you resolved it + * Whether any manual adjustments were needed beyond accepting one side + - Ask the user to review your conflict resolution summary before continuing + - After conflicts are resolved, continue with: + ```bash + git add + git cherry-pick --continue + ``` + +### Step 4: Push the backport branch + +Push to your fork (typically the remote that you have write access to): + +```bash +git push backport- +``` + +Example: `git push origin backport-26193` + +Note: If you're pushing to the official PowerShell repository and have permissions, you may push to `upstream` or the appropriate remote. + +### Step 5: Create the backport PR + +Create a new PR with the following format: + +**Title:** +``` +[] +``` + +Example: `[release/v7.4] GitHub Workflow cleanup` + +**Body:** +``` +Backport of # to + + + +Triggered by @ on behalf of @ + +Original CL Label: + +/cc @PowerShell/powershell-maintainers + +## Impact + +Choose either tooling or Customer impact. +### Tooling Impact + +- [ ] Required tooling change +- [ ] Optional tooling change (include reasoning) + +### Customer Impact + +- [ ] Customer reported +- [ ] Found internally + +[Select one or both of the boxes. Describe how this issue impacts customers, citing the expected and actual behaviors and scope of the issue. If customer-reported, provide the issue number.] + +## Regression + +- [ ] Yes +- [ ] No + +[If yes, specify when the regression was introduced. Provide the PR or commit if known.] + +## Testing + +[How was the fix verified? How was the issue missed previously? What tests were added?] + +## Risk + +- [ ] High +- [ ] Medium +- [ ] Low + +[High/Medium/Low. Justify the indication by mentioning how risks were measured and addressed.] +``` + +**Base branch:** `` (e.g., `release/v7.4`) + +**Head branch:** `backport-` (e.g., `backport-26193`) + +#### Guidelines for Filling Out the PR Body + +**For Impact Section**: +- If the original PR changed build/tooling/packaging, select "Tooling Impact" +- If it fixes a user-facing bug or changes user-visible behavior, select "Customer Impact" +- Copy relevant context from the original PR description +- Be specific about what changed and why + +**For Regression Section**: +- Mark "Yes" only if the original PR fixed a regression +- Include when the regression was introduced if known + +**For Testing Section**: +- Reference the original PR's testing approach +- Note any additional backport-specific testing needed +- Mention if manual testing was done to verify the backport + +**For Risk Assessment**: +- **High**: Changes core functionality, packaging, build systems, or security-related code +- **Medium**: Changes non-critical features, adds new functionality, or modifies existing behavior +- **Low**: Documentation, test-only changes, minor refactoring, or fixes with narrow scope +- Justify your assessment based on the scope of changes and potential impact + +**If there were merge conflicts**: +Add a note in the PR description after the Risk section describing what conflicts occurred and how they were resolved. + +### Step 6: Clean up temporary files + +After successful PR creation, clean up any temporary files created during the process: + +```powershell +Remove-Item pr*.diff -ErrorAction SilentlyContinue +``` + +## 5 — Definition of Done (self-check list) + +- [ ] Original PR is verified as merged +- [ ] Checked for existing backport PRs +- [ ] Reviewed backport labels to understand status +- [ ] Backport branch created from correct release branch +- [ ] Merge commit cherry-picked successfully (or conflicts resolved) +- [ ] If conflicts occurred, provided resolution summary to user +- [ ] Branch pushed to origin +- [ ] PR created with correct title format: `[] ` +- [ ] Temporary files cleaned up (pr*.diff) +- [ ] PR body includes: + - [ ] Backport reference: `Backport of (PR-number) to ` + - [ ] Auto-generated comment with original PR number + - [ ] Triggered by and original author attribution + - [ ] Original CL label (if available) + - [ ] CC to PowerShell maintainers + - [ ] Impact section filled out + - [ ] Regression section filled out + - [ ] Testing section filled out + - [ ] Risk section filled out +- [ ] Base branch set to target release branch +- [ ] No unrelated changes included + +## 6 — Branch naming convention + +**Format:** `backport-` + +Examples: +- `backport-26193` +- `backport-26334` + +Note: Automated bot uses format `backport/release/v/-`, but manual backports use the simpler `backport-` format. + +## 7 — Example backport PR + +Reference PR 26334 as the canonical example of a correct backport: + +**Original PR**: PR 26193 "GitHub Workflow cleanup" + +**Backport PR**: PR 26334 "[release/v7.4] GitHub Workflow cleanup" +- **Title**: `[release/v7.4] GitHub Workflow cleanup` +- **Body**: Started with backport reference to original PR and release branch +- **Branch**: `backport/release/v7.4/26193-4aff02475` (bot-created) +- **Base**: `release/v7.4` +- **Includes**: Auto-generated metadata, impact assessment, regression info, testing details, and risk level + +## 8 — Backport label system (for context) + +Backport labels follow pattern: `Backport-.x-` + +**Triage states:** +- `Consider` - Under review for backporting +- `Approved` - Approved and ready to be backported +- `Done` - Backport completed + +**Examples:** `Backport-7.4.x-Approved`, `Backport-7.5.x-Consider`, `Backport-7.3.x-Done` + +Note: The PowerShell repository has an automated bot (pwshBot) that creates backport PRs automatically when a merged PR has a backport approval label. Manual backports follow the same format. + +## Manual Backport Using PowerShell Tools + +For situations where automated backports fail or manual intervention is needed, use the `Invoke-PRBackport` function from `tools/releaseTools.psm1`. + +### Prerequisites + +1. **GitHub CLI**: Install from https://cli.github.com/ + - Required version: 2.17 or later + - Authenticate with `gh auth login` + +2. **Upstream Remote**: Configure a Git remote named `upstream` pointing to `PowerShell/PowerShell`: + ```powershell + git remote add upstream https://github.com/PowerShell/PowerShell.git + ``` + +### Using Invoke-PRBackport + +```powershell +# Import the release tools module +Import-Module ./tools/releaseTools.psm1 + +# Backport a single PR +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 + +# Backport with custom branch postfix +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 -BranchPostFix "retry" + +# Overwrite existing local branch if it exists +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 -Overwrite +``` + +### Parameters + +- **PrNumber** (Required): The PR number to backport +- **Target** (Required): Target release branch (must match pattern `release/v\d+\.\d+(\.\d+)?`) +- **Overwrite**: Switch to overwrite local branch if it already exists +- **BranchPostFix**: Add a postfix to the branch name (e.g., for retry attempts) +- **UpstreamRemote**: Name of the upstream remote (default: `upstream`) + +### How It Works + +1. Verifies the PR is merged +2. Fetches the target release branch from upstream +3. Creates a new branch: `backport-[-]` +4. Cherry-picks the merge commit +5. If conflicts occur, prompts you to resolve them +6. Creates the backport PR using GitHub CLI + +## Handling Merge Conflicts + +When cherry-picking fails due to conflicts: + +1. The script will pause and prompt you to fix conflicts +2. Resolve conflicts in your editor: + ```powershell + # Check which files have conflicts + git status + + # Edit files to resolve conflicts + # After resolving, stage the changes + git add + + # Continue the cherry-pick + git cherry-pick --continue + ``` +3. Type 'Yes' when prompted to continue the script +4. The script will create the PR + +### Understanding Conflict Patterns + +When resolving conflicts during backports, follow this approach: + +1. **Analyze the diff first**: Before resolving conflicts, fetch and review the original PR's diff to understand what changed: + ```powershell + gh pr diff --repo PowerShell/PowerShell | Out-File pr-diff.txt + ``` + +2. **Identify conflict types**: + - **Parameter additions**: New parameters added to functions (e.g., ValidateSet values) + - **Code removal**: Features removed in main but still exist in release branch + - **Code additions**: New code blocks that don't exist in release branch + - **Refactoring conflicts**: Code structure changes between branches + +3. **Resolution priorities**: + - Preserve the intent of the backported change + - Keep release branch-specific code that doesn't conflict with the fix + - When in doubt, favor the incoming change from the backport + - Document significant manual changes in the PR description + +4. **Verification**: + - After resolving conflicts, verify the file compiles/runs + - Check that the resolved code matches the original PR's intent + - Look for orphaned code that references removed functions + +5. **Create a conflict resolution summary**: + - List which files had conflicts + - Briefly explain the nature of each conflict + - Describe how you resolved it + - Ask user to review the resolution before continuing + +### Context-Aware Conflict Resolution + +**Key Principle**: The release branch may have different code than main. Your goal is to apply the *change* from the PR, not necessarily make the code identical to main. + +**Common Scenarios**: +1. **Function parameters differ**: If the release branch has fewer parameters than main, and the backport adds functionality unrelated to new parameters, keep the release branch parameters unless the new parameters are part of the fix +2. **Dependencies removed in main**: If main removed a dependency but the release branch still has it, and the backport is unrelated to that dependency, keep the release branch code +3. **New features in main**: If main has new features not in the release, focus on backporting only the specific fix, not the new features + +## Bulk Backporting Approved PRs + +To backport all PRs labeled as approved for a specific version: + +```powershell +Import-Module ./tools/releaseTools.psm1 + +# Backport all approved PRs for version 7.2.12 +Invoke-PRBackportApproved -Version 7.2.12 +``` + +This function: +1. Queries all merged PRs with the `Backport-.x-Approved` label +2. Attempts to backport each PR in order of merge date +3. Creates individual backport PRs for each + +## Viewing Backport Reports + +Get a list of PRs that need backporting: + +```powershell +Import-Module ./tools/releaseTools.psm1 + +# List all approved backports for 7.4 +Get-PRBackportReport -Version 7.4 -TriageState Approved + +# Open all approved backports in browser +Get-PRBackportReport -Version 7.4 -TriageState Approved -Web + +# Check which backports are done +Get-PRBackportReport -Version 7.4 -TriageState Done +``` + +## Branch Naming Conventions + +### Automated Bot Branches +Format: `backport/release/v/-` + +Example: `backport/release/v7.4/26193-4aff02475` + +### Manual Backport Branches +Format: `backport-[-]` + +Examples: +- `backport-26193` +- `backport-26193-retry` + +## PR Title and Description Format + +### Title +Format: `[release/v] ` + +Example: `[release/v7.4] GitHub Workflow cleanup` + +### Description +The backport PR description includes: +- Reference to original PR number +- Target release branch +- Auto-generated comment with original PR metadata +- Maintainer information +- Original CL label +- CC to PowerShell maintainers team + +Example description structure: +```text +Backport of (original-pr-number) to release/v + + + +Triggered by @ on behalf of @ + +Original CL Label: + +/cc @PowerShell/powershell-maintainers +``` + +## Best Practices + +1. **Verify PR is merged**: Only backport merged PRs +2. **Test backports**: Ensure backported changes work in the target release context +3. **Check for conflicts early**: Large PRs are more likely to have conflicts +4. **Use appropriate labels**: Apply correct version and triage state labels +5. **Document special cases**: If manual changes were needed, note them in the PR description +6. **Follow up on CI failures**: Backports should pass all CI checks before merging + +## Troubleshooting + +### "PR is not merged" Error +**Cause**: Attempting to backport a PR that hasn't been merged yet +**Solution**: Wait for the PR to be merged to the main branch first + +### "Please create an upstream remote" Error +**Cause**: No upstream remote configured +**Solution**: +```powershell +git remote add upstream https://github.com/PowerShell/PowerShell.git +git fetch upstream +``` + +### "GitHub CLI is not installed" Error +**Cause**: gh CLI not found in PATH +**Solution**: Install from https://cli.github.com/ and restart terminal + +### Cherry-pick Conflicts +**Cause**: Changes conflict with the target branch +**Solution**: Manually resolve conflicts, stage files, and continue cherry-pick + +### "Commit does not exist" Error +**Cause**: Local Git doesn't have the commit +**Solution**: +```powershell +git fetch upstream +``` + +## Related Resources + +- **Release Process**: See `docs/maintainers/releasing.md` +- **Release Tools**: See `tools/releaseTools.psm1` +- **Issue Management**: See `docs/maintainers/issue-management.md` From 69770b29b00d70b371a5f86b7a839d5f1afff7f1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:32:30 +0000 Subject: [PATCH 096/255] Remove obsolete test/docker/networktest directory (#26388) --- .../networktest/DockerRemoting.Tests.ps1 | 89 ------------------- test/docker/networktest/Dockerfile | 24 ----- 2 files changed, 113 deletions(-) delete mode 100644 test/docker/networktest/DockerRemoting.Tests.ps1 delete mode 100644 test/docker/networktest/Dockerfile diff --git a/test/docker/networktest/DockerRemoting.Tests.ps1 b/test/docker/networktest/DockerRemoting.Tests.ps1 deleted file mode 100644 index 5f5a13c5b4c..00000000000 --- a/test/docker/networktest/DockerRemoting.Tests.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -$imageName = "remotetestimage" -Describe "Basic remoting test with docker" -tags @("Scenario","Slow"){ - BeforeAll { - $Timeout = 600 # 10 minutes to run these tests - $dockerimage = docker images --format "{{ .Repository }}" $imageName - if ( $dockerimage -ne $imageName ) { - $pending = $true - Write-Warning "Docker image '$imageName' not found, not running tests" - return - } - else { - $pending = $false - } - - # give the containers something to do, otherwise they will exit and be removed - Write-Verbose -Verbose "setting up docker container PowerShell server" - $server = docker run -d $imageName powershell -c Start-Sleep -Seconds $timeout - Write-Verbose -Verbose "setting up docker container PowerShell client" - $client = docker run -d $imageName powershell -c Start-Sleep -Seconds $timeout - - # get fullpath to installed core powershell - Write-Verbose -Verbose "Getting path to PowerShell" - $powershellcorepath = docker exec $server powershell -c "(get-childitem 'c:\program files\powershell\*\pwsh.exe').fullname" - if ( ! $powershellcorepath ) - { - $pending = $true - Write-Warning "Cannot find powershell executable, not running tests" - return - } - $powershellcoreversion = ($powershellcorepath -split "[\\/]")[-2] - # we will need the configuration of the core powershell endpoint - $powershellcoreConfiguration = "powershell.${powershellcoreversion}" - - # capture the hostnames of the containers which will be used by the tests - Write-Verbose -Verbose "getting server hostname" - $serverhostname = docker exec $server hostname - Write-Verbose -Verbose "getting client hostname" - $clienthostname = docker exec $client hostname - - # capture the versions of full and core PowerShell - Write-Verbose -Verbose "getting powershell full version" - $fullVersion = docker exec $client powershell -c "`$PSVersionTable.psversion.tostring()" - if ( ! $fullVersion ) - { - $pending = $true - Write-Warning "Cannot determine PowerShell full version, not running tests" - return - } - - Write-Verbose -Verbose "getting powershell version" - $coreVersion = docker exec $client "$powershellcorepath" -c "`$PSVersionTable.psversion.tostring()" - if ( ! $coreVersion ) - { - $pending = $true - Write-Warning "Cannot determine PowerShell version, not running tests" - return - } - } - - AfterAll { - # to debug, comment out the following - if ( $pending -eq $false ) { - docker rm -f $server - docker rm -f $client - } - } - - It "Full powershell can get correct remote powershell version" -Pending:$pending { - $result = docker exec $client powershell -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -configurationname $powershellcoreConfiguration -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $coreVersion - } - - It "Full powershell can get correct remote powershell full version" -Pending:$pending { - $result = docker exec $client powershell -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $fullVersion - } - - It "Core powershell can get correct remote powershell version" -Pending:$pending { - $result = docker exec $client "$powershellcorepath" -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -configurationname $powershellcoreConfiguration -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $coreVersion - } - - It "Core powershell can get correct remote powershell full version" -Pending:$pending { - $result = docker exec $client "$powershellcorepath" -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $fullVersion - } -} diff --git a/test/docker/networktest/Dockerfile b/test/docker/networktest/Dockerfile deleted file mode 100644 index d558b139246..00000000000 --- a/test/docker/networktest/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# escape=` -FROM mcr.microsoft.com/windows/servercore:ltsc2022@sha256:5d09ffa90d91a46e2fe7652b0a37cbf5217f34a819c3d71cbe635dae8226061b - -SHELL ["pwsh.exe","-command"] - -# the source msi should change on a daily basis -# the destination should not change -ADD PSCore.msi /PSCore.msi - -# the msipath (PSCore.msi) below will need to change based on the daily package -# set up for basic auth -# install-powershellremoting will restart winrm service -RUN new-LocalUser -Name testuser -password (ConvertTo-SecureString 11aa!!AA -asplaintext -force); ` - add-localgroupmember -group administrators -member testuser; ` - set-item wsman:/localhost/service/auth/basic $true; ` - set-item WSMan:/localhost/client/trustedhosts "*" -force; ` - set-item wsman:/localhost/service/AllowUnencrypted $true; ` - set-item WSMan:/localhost/client/AllowUnencrypted $true; ` - Start-Process -FilePath msiexec.exe -ArgumentList '-qn', ` - '-i c:\PSCore.msi','-log c:\PSCore-install.log','-norestart' -wait ; ` - $psexec = get-item -path ${ENV:ProgramFiles}/powershell/*/pwsh.exe; ` - $corehome = $psexec.directory.fullname; ` - & $psexec Install-PowerShellRemoting.ps1; ` - remove-item -force c:\PSCore.msi From ac55117f073108dbfbbd4c5023396ab1bbb99b32 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 5 Nov 2025 11:23:17 -0800 Subject: [PATCH 097/255] Update backport prompt (#26392) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../backport-pr-to-release-branch.prompt.md | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/prompts/backport-pr-to-release-branch.prompt.md b/.github/prompts/backport-pr-to-release-branch.prompt.md index 8117bdc5183..32bff10bd5e 100644 --- a/.github/prompts/backport-pr-to-release-branch.prompt.md +++ b/.github/prompts/backport-pr-to-release-branch.prompt.md @@ -231,13 +231,42 @@ Choose either tooling or Customer impact. - **Medium**: Changes non-critical features, adds new functionality, or modifies existing behavior - **Low**: Documentation, test-only changes, minor refactoring, or fixes with narrow scope - Justify your assessment based on the scope of changes and potential impact +- **For CI/CD changes**: When backporting CI/CD infrastructure changes (workflows, build scripts, packaging), note in your justification that not taking these changes may create technical debt and make it difficult to apply future CI/CD changes that build on top of them. This doesn't change the risk level itself, but provides important context for why the change should be taken despite potentially higher risk **If there were merge conflicts**: Add a note in the PR description after the Risk section describing what conflicts occurred and how they were resolved. -### Step 6: Clean up temporary files +### Step 6: Add the CL label to the backport PR -After successful PR creation, clean up any temporary files created during the process: +After creating the backport PR, add the same changelog label (CL-*) from the original PR to the backport PR: + +```bash +gh pr edit --repo PowerShell/PowerShell --add-label "" +``` + +Example: `gh pr edit 26389 --repo PowerShell/PowerShell --add-label "CL-BuildPackaging"` + +This ensures the backport is properly categorized in the changelog for the release branch. + +### Step 7: Update the original PR's backport labels + +After successfully creating the backport PR, update the original PR to reflect that it has been backported: + +```bash +gh pr edit --repo PowerShell/PowerShell --add-label "Backport-.x-Migrated" --remove-label "Backport-.x-Consider" +``` + +Example: `gh pr edit 26193 --repo PowerShell/PowerShell --add-label "Backport-7.5.x-Migrated" --remove-label "Backport-7.5.x-Consider"` + +Notes: +- If the original PR had `Backport-.x-Approved` instead of `Consider`, remove that label +- This step helps track which PRs have been successfully backported +- The `Migrated` label indicates the backport PR has been created (not necessarily merged) +- The `Done` label should only be added once the backport PR is merged + +### Step 8: Clean up temporary files + +After successful PR creation and labeling, clean up any temporary files created during the process: ```powershell Remove-Item pr*.diff -ErrorAction SilentlyContinue @@ -253,6 +282,8 @@ Remove-Item pr*.diff -ErrorAction SilentlyContinue - [ ] If conflicts occurred, provided resolution summary to user - [ ] Branch pushed to origin - [ ] PR created with correct title format: `[] ` +- [ ] CL label added to backport PR (matching original PR's CL label) +- [ ] Original PR labels updated (added Migrated, removed Consider/Approved) - [ ] Temporary files cleaned up (pr*.diff) - [ ] PR body includes: - [ ] Backport reference: `Backport of (PR-number) to ` @@ -269,13 +300,13 @@ Remove-Item pr*.diff -ErrorAction SilentlyContinue ## 6 — Branch naming convention -**Format:** `backport-` +**Format:** `backport/release//pr/` Examples: -- `backport-26193` -- `backport-26334` +- `backport/release/v7.5/pr/26193` +- `backport/release/v7.4.1/pr/26334` -Note: Automated bot uses format `backport/release/v/-`, but manual backports use the simpler `backport-` format. +Note: Automated bot uses format `backport/release/v/-`, but manual backports should use the format `backport/release//pr/` as shown above. ## 7 — Example backport PR From 000c116e5164365e14539ae6f5785011ffd7d098 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 5 Nov 2025 19:10:55 -0500 Subject: [PATCH 098/255] Add Fuzz Tests (#26384) Co-authored-by: anamnavi --- .../AssemblyInfo.cs | 1 + .../common/RemoteSessionHyperVSocket.cs | 35 +++++++-- test/fuzzing/FuzzingApp/Program.cs | 45 +++++++++++ test/fuzzing/FuzzingApp/Target.cs | 19 +++++ .../FuzzingApp/powershell-fuzz-tests.csproj | 29 +++++++ test/fuzzing/inputs/maxinput | 1 + test/fuzzing/inputs/mininput | 1 + test/fuzzing/runFuzzer.ps1 | 75 +++++++++++++++++++ 8 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 test/fuzzing/FuzzingApp/Program.cs create mode 100644 test/fuzzing/FuzzingApp/Target.cs create mode 100644 test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj create mode 100644 test/fuzzing/inputs/maxinput create mode 100644 test/fuzzing/inputs/mininput create mode 100644 test/fuzzing/runFuzzer.ps1 diff --git a/src/System.Management.Automation/AssemblyInfo.cs b/src/System.Management.Automation/AssemblyInfo.cs index 559d809fe72..e265bb453c8 100644 --- a/src/System.Management.Automation/AssemblyInfo.cs +++ b/src/System.Management.Automation/AssemblyInfo.cs @@ -7,6 +7,7 @@ [assembly: InternalsVisibleTo("powershell-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("powershell-perf,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("powershell-fuzz-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs index cd18dc511b1..8d53adf0ca8 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs @@ -434,7 +434,10 @@ internal static void ValidateToken(Socket socket, string token, DateTimeOffset t // Final check if we got the token before the timeout cancellationToken.ThrowIfCancellationRequested(); - if (string.IsNullOrEmpty(responseString) || !responseString.StartsWith("TOKEN ", StringComparison.Ordinal)) + ReadOnlySpan responseBytes = Encoding.UTF8.GetBytes(responseString); + string responseToken = RemoteSessionHyperVSocketClient.ExtractToken(responseBytes); + + if (responseToken == null) { socket.Send("FAIL"u8); // If the response is not in the expected format, we throw an exception. @@ -444,9 +447,6 @@ internal static void ValidateToken(Socket socket, string token, DateTimeOffset t PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Token Response")); } - // Extract the token from the response. - string responseToken = responseString.Substring(6).Trim(); - if (!string.Equals(responseToken, token, StringComparison.Ordinal)) { socket.Send("FAIL"u8); @@ -1059,14 +1059,18 @@ public static (bool success, string authenticationToken) ExchangeCredentialsAndC // allowing a significant larger size, allows the broker to make almost arbitrary changes, // without breaking the client. string token = ReceiveResponse(HyperVSocket, 1024); // either "PASS" or "FAIL" - if (token == null || !token.StartsWith("TOKEN ", StringComparison.Ordinal)) + + ReadOnlySpan tokenResponseBytes = Encoding.UTF8.GetBytes(token); + string extractedToken = ExtractToken(tokenResponseBytes); + + if (extractedToken == null) { s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server did not respond with a valid token. Response: {0}", token); throw new PSDirectException( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Token " + token)); } - token = token.Substring(6); // remove "TOKEN " prefix + token = extractedToken; HyperVSocket.Send("PASS"u8); // acknowledge the token return (true, token); @@ -1122,6 +1126,25 @@ internal static string ReceiveResponse(Socket socket, int bufferSize) } } + internal static string ExtractToken(ReadOnlySpan tokenResponse) + { + string token = Encoding.UTF8.GetString(tokenResponse); + + if (token == null || !token.StartsWith("TOKEN ", StringComparison.Ordinal)) + { + return null; // caller method will write trace (and determine when to expose token info as appropriate) + } + + token = token.Substring(6).Trim(); // remove "TOKEN " prefix + + if (token.Length == 0) + { + return null; + } + + return token; + } + /// /// Sends user data (domain, username, etc.) over the HyperVSocket using Unicode encoding. /// diff --git a/test/fuzzing/FuzzingApp/Program.cs b/test/fuzzing/FuzzingApp/Program.cs new file mode 100644 index 00000000000..77dee97165c --- /dev/null +++ b/test/fuzzing/FuzzingApp/Program.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Text; +using SharpFuzz; +using System.Management.Automation.Remoting; + +namespace FuzzTests +{ + public static class Program + { + public static void Main(string[] args) + { + FuzzTargetMethod(args); + } + + public static void FuzzTargetMethod(string[] args) + { + if (args == null) + { + Console.WriteLine("args was null"); + args = Array.Empty(); + } + + try + { + Fuzzer.LibFuzzer.Run(Target.ExtractToken); + } + catch (System.ArgumentNullException nex) + { + Console.WriteLine($"ArgumentNullException in main: {nex.Message}"); + Console.WriteLine($"Stack Trace: {nex.StackTrace}"); + Environment.Exit(1); + } + catch (Exception ex) + { + Console.WriteLine($"Exception in main: {ex.Message}"); + Console.WriteLine($"Exception type: {ex.GetType()}"); + Console.WriteLine($"Stack Trace: {ex.StackTrace}"); + Environment.Exit(1); + } + } + } +} diff --git a/test/fuzzing/FuzzingApp/Target.cs b/test/fuzzing/FuzzingApp/Target.cs new file mode 100644 index 00000000000..033f6517620 --- /dev/null +++ b/test/fuzzing/FuzzingApp/Target.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Text; +using SharpFuzz; +using System.Management.Automation.Remoting; + +namespace FuzzTests +{ + public static class Target + { + public static void ExtractToken(ReadOnlySpan tokenResponse) + { + RemoteSessionHyperVSocketClient.ExtractToken(tokenResponse); + } + } +} + diff --git a/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj new file mode 100644 index 00000000000..fc6549122f5 --- /dev/null +++ b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj @@ -0,0 +1,29 @@ + + + + PowerShell Fuzzing + powershell-fuzz-tests + + + + Exe + net10.0 + enable + enable + + + + true + ../../../src/signing/visualstudiopublic.snk + true + + + + + + + + + + + diff --git a/test/fuzzing/inputs/maxinput b/test/fuzzing/inputs/maxinput new file mode 100644 index 00000000000..6773a896b3c --- /dev/null +++ b/test/fuzzing/inputs/maxinput @@ -0,0 +1 @@ +TOKEN abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/= \ No newline at end of file diff --git a/test/fuzzing/inputs/mininput b/test/fuzzing/inputs/mininput new file mode 100644 index 00000000000..c9b91c737c2 --- /dev/null +++ b/test/fuzzing/inputs/mininput @@ -0,0 +1 @@ +TOKEN TQ \ No newline at end of file diff --git a/test/fuzzing/runFuzzer.ps1 b/test/fuzzing/runFuzzer.ps1 new file mode 100644 index 00000000000..94000b59bbb --- /dev/null +++ b/test/fuzzing/runFuzzer.ps1 @@ -0,0 +1,75 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +param ( + [string]$libFuzzer = ".\libfuzzer-dotnet-windows.exe", + [string]$project = ".\FuzzingApp\powershell-fuzz-tests.csproj", + [Parameter(Mandatory=$true)] + [string]$corpus, + [string]$command = "sharpfuzz.exe" +) + +Set-StrictMode -Version Latest + +$outputDir = "out" + +if (Test-Path $outputDir) { + Remove-Item -Recurse -Force $outputDir +} + +Write-Host "dotnet publish $project -c release -o $outputDir" +dotnet publish $project -c release -o $outputDir +Write-Host "build completed" + +$projectName = (Get-Item $project).BaseName +$projectDll = "$projectName.dll" +$project = Join-Path $outputDir $projectDll + +$exclusions = @( + "dnlib.dll", + "SharpFuzz.dll", + "SharpFuzz.Common.dll" +) + +$exclusions += $projectDll + +Write-Host "instrumenting: $project" +& $command $project Target +Write-Host "done instrumenting $project" + +$fuzzingTargets = Get-ChildItem $outputDir -Filter *.dll ` +| Where-Object { $_.Name -notin $exclusions } ` +| Where-Object { $_.Name -notlike "System.*.dll" } +| Where-Object { $_.Name -notlike "Newtonsoft.*.dll" } +| Where-Object { $_.Name -notlike "Microsoft.*.dll" } + +foreach ($fuzzingTarget in $fuzzingTargets) { + Write-Output "Instrumenting $fuzzingTarget" + & $command $fuzzingTarget.FullName + + if ($LastExitCode -ne 0) { + Write-Error "An error occurred while instrumenting $fuzzingTarget" + exit 1 + } +} + +$smaDllPath = Join-Path $outputDir "System.Management.Automation.dll" + +Write-Host "instrumenting: $smaDllPath" +& $command $smaDllPath Remoting +Write-Host "done instrumenting: $smaDllPath" + +$fuzzingTargets += $projectDll +$fuzzingTargets += $smaDllPath + +if (($fuzzingTargets | Measure-Object).Count -eq 0) { + Write-Error "No fuzzing targets found" + exit 1 +} + +$outputPath = Join-Path $outputDir "output.txt" + +Write-Host "launching fuzzer on $project" +Write-Host "$libFuzzer --target_path=dotnet --target_arg=$project $corpus" +& $libFuzzer --target_path=dotnet --target_arg=$project $corpus -max_len=1024 2>&1 ` +| Tee-Object -FilePath $outputPath From 3953976592f94c72e6ab85c957c6458674429483 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 6 Nov 2025 16:36:54 -0800 Subject: [PATCH 099/255] Fix the fuzzy test (#26402) --- test/fuzzing/FuzzingApp/Program.cs | 6 +- test/fuzzing/FuzzingApp/Target.cs | 3 - .../FuzzingApp/powershell-fuzz-tests.csproj | 8 +- test/fuzzing/runFuzzer.ps1 | 80 ++++++++----------- 4 files changed, 39 insertions(+), 58 deletions(-) diff --git a/test/fuzzing/FuzzingApp/Program.cs b/test/fuzzing/FuzzingApp/Program.cs index 77dee97165c..4e309ffc468 100644 --- a/test/fuzzing/FuzzingApp/Program.cs +++ b/test/fuzzing/FuzzingApp/Program.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System; -using System.Text; using SharpFuzz; -using System.Management.Automation.Remoting; namespace FuzzTests { @@ -27,7 +25,7 @@ public static void FuzzTargetMethod(string[] args) { Fuzzer.LibFuzzer.Run(Target.ExtractToken); } - catch (System.ArgumentNullException nex) + catch (ArgumentNullException nex) { Console.WriteLine($"ArgumentNullException in main: {nex.Message}"); Console.WriteLine($"Stack Trace: {nex.StackTrace}"); @@ -41,5 +39,5 @@ public static void FuzzTargetMethod(string[] args) Environment.Exit(1); } } - } + } } diff --git a/test/fuzzing/FuzzingApp/Target.cs b/test/fuzzing/FuzzingApp/Target.cs index 033f6517620..d82e17e8702 100644 --- a/test/fuzzing/FuzzingApp/Target.cs +++ b/test/fuzzing/FuzzingApp/Target.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. using System; -using System.Text; -using SharpFuzz; using System.Management.Automation.Remoting; namespace FuzzTests @@ -16,4 +14,3 @@ public static void ExtractToken(ReadOnlySpan tokenResponse) } } } - diff --git a/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj index fc6549122f5..7ee82c34cae 100644 --- a/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj +++ b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj @@ -1,15 +1,11 @@  + + PowerShell Fuzzing powershell-fuzz-tests - - - Exe - net10.0 - enable - enable diff --git a/test/fuzzing/runFuzzer.ps1 b/test/fuzzing/runFuzzer.ps1 index 94000b59bbb..5cb63393c4c 100644 --- a/test/fuzzing/runFuzzer.ps1 +++ b/test/fuzzing/runFuzzer.ps1 @@ -1,75 +1,65 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +<# +1. libfuzzer-dotnet-windows.exe can be installed from https://github.com/Metalnem/libfuzzer-dotnet/releases + +2. sharpfuzz can be installed with dotnet-tool: + > dotnet tool install --global SharpFuzz.CommandLine --version 2.2.0 + +Usage: sharpfuzz [path-to-assembly] [prefix ...] + +path-to-assembly: + The path to an assembly .dll file to instrument. + +prefix: + The class or the namespace to instrument. + If not present, all types in the assembly will be instrumented. + At least one prefix is required when instrumenting System.Private.CoreLib. + +Examples: + sharpfuzz Newtonsoft.Json.dll + sharpfuzz System.Private.CoreLib.dll System.Number + sharpfuzz System.Private.CoreLib.dll System.DateTimeFormat System.DateTimeParse +#> + param ( - [string]$libFuzzer = ".\libfuzzer-dotnet-windows.exe", - [string]$project = ".\FuzzingApp\powershell-fuzz-tests.csproj", - [Parameter(Mandatory=$true)] - [string]$corpus, + [string]$libFuzzer = "$PSScriptRoot\libfuzzer-dotnet-windows.exe", + [string]$project = "$PSScriptRoot\FuzzingApp\powershell-fuzz-tests.csproj", + [string]$corpus = "$PSScriptRoot\inputs", [string]$command = "sharpfuzz.exe" ) Set-StrictMode -Version Latest -$outputDir = "out" +$outputDir = "$PSScriptRoot\out" if (Test-Path $outputDir) { Remove-Item -Recurse -Force $outputDir } -Write-Host "dotnet publish $project -c release -o $outputDir" -dotnet publish $project -c release -o $outputDir +Write-Host "dotnet publish $project -c Debug -o $outputDir" +dotnet publish $project -c Debug -o $outputDir Write-Host "build completed" $projectName = (Get-Item $project).BaseName $projectDll = "$projectName.dll" $project = Join-Path $outputDir $projectDll +$smaDllPath = Join-Path $outputDir "System.Management.Automation.dll" -$exclusions = @( - "dnlib.dll", - "SharpFuzz.dll", - "SharpFuzz.Common.dll" -) - -$exclusions += $projectDll - +## Instrument the specific class within the test assembly. Write-Host "instrumenting: $project" -& $command $project Target +## !NOTE! If you instrument the class that defines "Main", it will fail. +& $command $project "FuzzTests.Target" Write-Host "done instrumenting $project" -$fuzzingTargets = Get-ChildItem $outputDir -Filter *.dll ` -| Where-Object { $_.Name -notin $exclusions } ` -| Where-Object { $_.Name -notlike "System.*.dll" } -| Where-Object { $_.Name -notlike "Newtonsoft.*.dll" } -| Where-Object { $_.Name -notlike "Microsoft.*.dll" } - -foreach ($fuzzingTarget in $fuzzingTargets) { - Write-Output "Instrumenting $fuzzingTarget" - & $command $fuzzingTarget.FullName - - if ($LastExitCode -ne 0) { - Write-Error "An error occurred while instrumenting $fuzzingTarget" - exit 1 - } -} - -$smaDllPath = Join-Path $outputDir "System.Management.Automation.dll" - +## Instrument any other assemblies that need to be tested. Write-Host "instrumenting: $smaDllPath" -& $command $smaDllPath Remoting +& $command $smaDllPath "System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient" Write-Host "done instrumenting: $smaDllPath" -$fuzzingTargets += $projectDll -$fuzzingTargets += $smaDllPath - -if (($fuzzingTargets | Measure-Object).Count -eq 0) { - Write-Error "No fuzzing targets found" - exit 1 -} - $outputPath = Join-Path $outputDir "output.txt" Write-Host "launching fuzzer on $project" Write-Host "$libFuzzer --target_path=dotnet --target_arg=$project $corpus" -& $libFuzzer --target_path=dotnet --target_arg=$project $corpus -max_len=1024 2>&1 ` -| Tee-Object -FilePath $outputPath +& $libFuzzer --target_path=dotnet --target_arg=$project $corpus -max_len=1024 2>&1 | Tee-Object -FilePath $outputPath From e7bf5621bf8c3dfe7c4bdc69f83178077ec7bd5d Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 7 Nov 2025 11:53:43 -0800 Subject: [PATCH 100/255] Improve ADO package build and validation across platforms (#26398) --- .github/workflows/macos-ci.yml | 2 +- .pipelines/templates/mac-package-build.yml | 26 ++++-- build.psm1 | 11 ++- .../macos/package-validation.tests.ps1 | 81 ++++++++++++------- tools/packaging/packaging.psm1 | 70 ++++++++-------- 5 files changed, 116 insertions(+), 74 deletions(-) rename tools/packaging/releaseTests/macOSPackage.tests.ps1 => test/packaging/macos/package-validation.tests.ps1 (85%) diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 2ee96079049..bdb7c128d12 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -205,7 +205,7 @@ jobs: } Import-Module Pester $pesterConfig = New-PesterConfiguration - $pesterConfig.Run.Path = './tools/packaging/releaseTests/macOSPackage.tests.ps1' + $pesterConfig.Run.Path = './test/packaging/macos/package-validation.tests.ps1' $pesterConfig.Run.PassThru = $true $pesterConfig.Output.Verbosity = 'Detailed' $pesterConfig.TestResult.Enabled = $true diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index 669ab3c8437..d429be91284 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -116,13 +116,25 @@ jobs: Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS $pkgNameFilter = "powershell-*$macosRuntime.pkg" - $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$pkgPath" + Write-Verbose -Verbose "Looking for pkg packages with filter: $pkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File + + foreach($p in $pkgPath) { + $file = $p.FullName + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } Start-PSPackage -Type tar -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS $tarPkgNameFilter = "powershell-*$macosRuntime.tar.gz" - $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$tarPkgPath" + Write-Verbose -Verbose "Looking for tar packages with filter: $tarPkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File + + foreach($t in $tarPkgPath) { + $file = $t.FullName + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } displayName: 'Package ${{ parameters.buildArchitecture}}' env: @@ -195,14 +207,14 @@ jobs: - pwsh: | $signedPkg = Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*osx*.zip" -File - + $signedPkg | ForEach-Object { Write-Verbose -Verbose "Signed package zip: $_" - + if (-not (Test-Path $_)) { throw "Package not found: $_" } - + if (-not (Test-Path $(ob_outputDirectory))) { $null = New-Item -Path $(ob_outputDirectory) -ItemType Directory } diff --git a/build.psm1 b/build.psm1 index c56303524be..dd2cf0f351e 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2399,7 +2399,7 @@ function Start-PSBootstrap { Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y rpm-build")) -IgnoreExitcode } } - + # For Debian-based systems and Mariner, ensure dpkg-deb is available if ($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) { Write-Verbose -Verbose "Checking for dpkg-deb..." @@ -2407,9 +2407,14 @@ function Start-PSBootstrap { Write-Warning "dpkg-deb not found. Installing dpkg package..." if ($environment.IsMariner) { # For Mariner (Azure Linux), install the extended repo first to access dpkg. + Write-Verbose -verbose "BEGIN: /etc/os-release content:" + Get-Content /etc/os-release | Write-Verbose -verbose + Write-Verbose -verbose "END: /etc/os-release content" + Write-Verbose -Verbose "Installing azurelinux-repos-extended for Mariner..." - Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y azurelinux-repos-extended")) -IgnoreExitcode - Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y dpkg")) -IgnoreExitcode + + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager azurelinux-repos-extended")) -IgnoreExitcode -Verbose + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager dpkg")) -IgnoreExitcode -Verbose } else { Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo apt-get install -y dpkg")) -IgnoreExitcode } diff --git a/tools/packaging/releaseTests/macOSPackage.tests.ps1 b/test/packaging/macos/package-validation.tests.ps1 similarity index 85% rename from tools/packaging/releaseTests/macOSPackage.tests.ps1 rename to test/packaging/macos/package-validation.tests.ps1 index c1de1091562..02b09fbb078 100644 --- a/tools/packaging/releaseTests/macOSPackage.tests.ps1 +++ b/test/packaging/macos/package-validation.tests.ps1 @@ -1,51 +1,54 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + Describe "Verify macOS Package" { BeforeAll { Write-Verbose "In Describe BeforeAll" -Verbose Import-Module $PSScriptRoot/../../../build.psm1 - + # Find the macOS package $packagePath = $env:PACKAGE_FOLDER if (-not $packagePath) { $packagePath = Get-Location } - + Write-Verbose "Looking for package in: $packagePath" -Verbose $package = Get-ChildItem -Path $packagePath -Filter "*.pkg" -ErrorAction SilentlyContinue | Select-Object -First 1 - + if (-not $package) { Write-Warning "No .pkg file found in $packagePath" } else { Write-Verbose "Found package: $($package.FullName)" -Verbose } - + # Set up test directories $script:package = $package $script:expandDir = $null $script:payloadDir = $null $script:extractedFiles = @() - + if ($package) { # Use TestDrive for temporary directories - pkgutil will create the expand directory $script:expandDir = Join-Path "TestDrive:" -ChildPath "package-contents-test" $expandDirResolved = (Resolve-Path "TestDrive:").ProviderPath $script:expandDir = Join-Path $expandDirResolved -ChildPath "package-contents-test" - + Write-Verbose "Expanding package to: $($script:expandDir)" -Verbose # pkgutil will create the directory itself, so don't pre-create it Start-NativeExecution { pkgutil --expand $package.FullName $script:expandDir } - + # Extract the payload to verify files $script:payloadDir = Join-Path "TestDrive:" -ChildPath "package-payload-test" $payloadDirResolved = (Resolve-Path "TestDrive:").ProviderPath $script:payloadDir = Join-Path $payloadDirResolved -ChildPath "package-payload-test" - + # Create payload directory since cpio needs it if (-not (Test-Path $script:payloadDir)) { $null = New-Item -ItemType Directory -Path $script:payloadDir -Force } - + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse | Select-Object -First 1 if ($componentPkg) { Write-Verbose "Extracting payload from: $($componentPkg.FullName)" -Verbose @@ -57,40 +60,60 @@ Describe "Verify macOS Package" { Pop-Location } } - + # Get all extracted files for verification $script:extractedFiles = Get-ChildItem -Path $script:payloadDir -Recurse -ErrorAction SilentlyContinue Write-Verbose "Extracted $($script:extractedFiles.Count) files" -Verbose } } - + AfterAll { # TestDrive automatically cleans up, but we can ensure cleanup happens # No manual cleanup needed as TestDrive handles it } - + Context "Package existence and structure" { It "Package file should exist" { $script:package | Should -Not -BeNullOrEmpty -Because "A .pkg file should be created" $script:package.Extension | Should -Be ".pkg" } - + + It "Package name should follow correct naming convention" { + $script:package | Should -Not -BeNullOrEmpty + + # Regex pattern for valid macOS PKG package names. + # Valid examples: + # - powershell-7.4.13-osx-x64.pkg (Intel x64 - note: x64 with hyphens for compatibility) + # - powershell-7.4.13-osx-arm64.pkg (Apple Silicon) + # - powershell-preview-7.6.0-preview.6-osx-x64.pkg + # - powershell-lts-7.4.13-osx-arm64.pkg + $pkgPackageNamePattern = '^powershell(-preview|-lts)?-\d+\.\d+\.\d+(-[a-z]+\.\d+)?-osx-(x64|arm64)\.pkg$' + + $script:package.Name | Should -Match $pkgPackageNamePattern -Because "Package name should follow the standard naming convention" + } + + It "Package name should NOT use x86_64 with underscores" { + $script:package | Should -Not -BeNullOrEmpty + + $script:package.Name | Should -Not -Match 'x86_64' -Because "Package should use 'x64' not 'x86_64' (with underscores) for compatibility" + } + It "Package should expand successfully" { $script:expandDir | Should -Exist Get-ChildItem -Path $script:expandDir | Should -Not -BeNullOrEmpty } - + It "Package should have a component package" { $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse -ErrorAction SilentlyContinue $componentPkg | Should -Not -BeNullOrEmpty -Because "Package should contain a component.pkg" } - + It "Payload should extract successfully" { $script:payloadDir | Should -Exist $script:extractedFiles | Should -Not -BeNullOrEmpty -Because "Package payload should contain files" } } - + Context "Required files in package" { BeforeAll { $expectedFilePatterns = @{ @@ -99,7 +122,7 @@ Describe "Verify macOS Package" { "Man page" = "usr/local/share/man/man1/pwsh*.gz" "Launcher application plist" = "Applications/PowerShell*.app/Contents/Info.plist" } - + $testCases = @() foreach ($key in $expectedFilePatterns.Keys) { $testCases += @{ @@ -107,23 +130,23 @@ Describe "Verify macOS Package" { Pattern = $expectedFilePatterns[$key] } } - + $script:testCases = $testCases } - + It "Should contain " -TestCases $script:testCases { param($Description, $Pattern) - + $found = $script:extractedFiles | Where-Object { $_.FullName -like "*$Pattern*" } $found | Should -Not -BeNullOrEmpty -Because "$Description should exist in the package at path matching '$Pattern'" } } - + Context "PowerShell binary verification" { It "PowerShell executable should be executable" { $pwshBinary = $script:extractedFiles | Where-Object { $_.FullName -like "*/pwsh" -and $_.FullName -like "*/microsoft/powershell/*" } $pwshBinary | Should -Not -BeNullOrEmpty - + # Check if file has executable permissions (on Unix-like systems) if ($IsLinux -or $IsMacOS) { $permissions = (Get-Item $pwshBinary[0].FullName).UnixFileMode @@ -132,27 +155,27 @@ Describe "Verify macOS Package" { } } } - + Context "Launcher application" { It "Launcher app should have proper bundle structure" { $plistFile = $script:extractedFiles | Where-Object { $_.FullName -like "*PowerShell*.app/Contents/Info.plist" } $plistFile | Should -Not -BeNullOrEmpty - + # Verify the bundle has required components $appPath = Split-Path (Split-Path $plistFile[0].FullName -Parent) -Parent $macOSDir = Join-Path $appPath "Contents/MacOS" $resourcesDir = Join-Path $appPath "Contents/Resources" - + Test-Path $macOSDir | Should -Be $true -Because "App bundle should have Contents/MacOS directory" Test-Path $resourcesDir | Should -Be $true -Because "App bundle should have Contents/Resources directory" } - + It "Launcher script should exist and be executable" { - $launcherScript = $script:extractedFiles | Where-Object { - $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" + $launcherScript = $script:extractedFiles | Where-Object { + $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" } $launcherScript | Should -Not -BeNullOrEmpty -Because "Launcher script should exist" - + if ($IsLinux -or $IsMacOS) { $permissions = (Get-Item $launcherScript[0].FullName).UnixFileMode $permissions.ToString() | Should -Match 'x' -Because "Launcher script should have execute permissions" diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index ccdc4e1cf08..e229aa493ae 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1239,15 +1239,15 @@ function New-UnixPackage { # Use rpmbuild directly for RPM packages if ($PSCmdlet.ShouldProcess("Create RPM package with rpmbuild")) { Write-Log "Creating RPM package with rpmbuild..." - + # Create rpmbuild directory structure $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" $specsDir = Join-Path $rpmBuildRoot "SPECS" $rpmsDir = Join-Path $rpmBuildRoot "RPMS" - + New-Item -ItemType Directory -Path $specsDir -Force | Out-Null New-Item -ItemType Directory -Path $rpmsDir -Force | Out-Null - + # Generate RPM spec file $specContent = New-RpmSpec ` -Name $Name ` @@ -1264,11 +1264,11 @@ function New-UnixPackage { -LinkInfo $Links ` -Distribution $DebDistro ` -HostArchitecture $HostArchitecture - + $specFile = Join-Path $specsDir "$Name.spec" $specContent | Out-File -FilePath $specFile -Encoding ascii Write-Verbose "Generated spec file: $specFile" -Verbose - + # Log the spec file content if ($env:GITHUB_ACTIONS -eq 'true') { Write-Host "::group::RPM Spec File Content" @@ -1277,7 +1277,7 @@ function New-UnixPackage { } else { Write-Verbose "RPM Spec File Content:`n$specContent" -Verbose } - + # Build RPM package try { # Use bash to properly handle rpmbuild arguments @@ -1290,16 +1290,16 @@ function New-UnixPackage { Write-Verbose "Running: $buildCmd" -Verbose $Output = bash -c $buildCmd 2>&1 $exitCode = $LASTEXITCODE - + if ($exitCode -ne 0) { throw "rpmbuild failed with exit code $exitCode" } - + # Find the generated RPM - $rpmFile = Get-ChildItem -Path (Join-Path $rpmsDir $HostArchitecture) -Filter "*.rpm" -ErrorAction Stop | - Sort-Object -Property LastWriteTime -Descending | + $rpmFile = Get-ChildItem -Path (Join-Path $rpmsDir $HostArchitecture) -Filter "*.rpm" -ErrorAction Stop | + Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 - + if ($rpmFile) { # Copy RPM to current location Copy-Item -Path $rpmFile.FullName -Destination $CurrentLocation -Force @@ -1337,7 +1337,7 @@ function New-UnixPackage { -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` -HostArchitecture $HostArchitecture ` -CurrentLocation $CurrentLocation - + $Output = @("Created package {:path=>""$($result.PackageName)""}") } catch { @@ -1348,7 +1348,7 @@ function New-UnixPackage { # Use native macOS packaging tools if ($PSCmdlet.ShouldProcess("Create macOS package with pkgbuild/productbuild")) { Write-Log "Creating macOS package with native tools..." - + $macPkgArgs = @{ Name = $Name Version = $packageVersion @@ -1363,7 +1363,7 @@ function New-UnixPackage { HostArchitecture = $HostArchitecture CurrentLocation = $CurrentLocation } - + try { $packageFile = New-MacOSPackage @macPkgArgs $Output = @("Created package {:path=>""$($packageFile.Name)""}") @@ -1386,7 +1386,7 @@ function New-UnixPackage { Clear-MacOSLauncher } } - + # Clean up rpmbuild directory if it was created if ($Type -eq 'rpm') { $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" @@ -1395,7 +1395,7 @@ function New-UnixPackage { Remove-Item -Path $rpmBuildRoot -Recurse -Force -ErrorAction SilentlyContinue } } - + if ($AfterScriptInfo.AfterInstallScript) { Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterInstallScript -Force } @@ -1489,13 +1489,13 @@ function New-MacOsDistributionPackage $resourcesDir = Join-Path -Path $tempDir -ChildPath 'resources' New-Item -ItemType Directory -Path $resourcesDir -Force > $null - + # Copy background file to temp directory $backgroundFile = "$RepoRoot/assets/macDialog.png" if (Test-Path $backgroundFile) { Copy-Item -Path $backgroundFile -Destination $resourcesDir -Force } - + # Copy the component package to temp directory $componentFileName = Split-Path -Leaf -Path $ComponentPackage $tempComponentPath = Join-Path -Path $tempDir -ChildPath $componentFileName @@ -1511,7 +1511,7 @@ function New-MacOsDistributionPackage # Minimum OS version $minOSVersion = "11.0" # macOS Big Sur minimum - + # format distribution template with: # 0 - title # 1 - version @@ -1522,8 +1522,10 @@ function New-MacOsDistributionPackage $PackagingStrings.OsxDistributionTemplate -f $PackageName, $Version, $componentFileName, $minOSVersion, $PackageIdentifier, $HostArchitecture | Out-File -Encoding utf8 -FilePath $distributionXmlPath -Force # Build final package path - $finalPackagePath = Join-Path $OutputDirectory "$PackageName-$Version-osx-$HostArchitecture.pkg" - + # Rename x86_64 to x64 for compatibility + $packageArchName = if ($HostArchitecture -eq "x86_64") { "x64" } else { $HostArchitecture } + $finalPackagePath = Join-Path $OutputDirectory "$PackageName-$Version-osx-$packageArchName.pkg" + # Remove existing package if it exists if (Test-Path $finalPackagePath) { Write-Warning "Removing existing package: $finalPackagePath" @@ -1542,7 +1544,7 @@ function New-MacOsDistributionPackage --resources $resourcesDir ` $finalPackagePath } - + if (Test-Path $finalPackagePath) { Write-Log "Successfully created macOS package: $finalPackagePath" } @@ -1612,7 +1614,7 @@ function New-RpmSpec # RPM doesn't allow hyphens in version, so convert them to underscores # e.g., "7.6.0-preview.6" becomes Version: 7.6.0_preview.6 $rpmVersion = $Version -replace '-', '_' - + # Build Release field with distribution suffix (e.g., "1.cm" or "1.rh") # Don't use RPM macros - build the full release string in PowerShell $rpmRelease = "$Iteration.$Distribution" @@ -1638,7 +1640,7 @@ AutoReq: no } else { # For cross-architecture builds, don't specify BuildArch in spec # The --target option will handle the architecture - + # Disable automatic binary stripping for cross-arch builds # The native /bin/strip on x86_64 cannot process ARM64 binaries and would fail with: # "Unable to recognise the format of the input file" @@ -1646,7 +1648,7 @@ AutoReq: no # __strip: This macro controls the command used for stripping binaries during the build process. # /bin/true: A command that does nothing and always exits successfully, effectively bypassing the stripping process. $specContent += "%define __strip /bin/true`n" - + # Disable debug package generation to prevent strip-related errors # Debug packages require binary stripping which fails for cross-arch builds # See: https://rpm-packaging-guide.github.io/#debugging @@ -1903,11 +1905,11 @@ $(if ($extendedDescription) { $extendedDescription + "`n" }) # Copy DEBIAN directory and data files to build root $buildDir = Join-Path $debBuildRoot "build" New-Item -ItemType Directory -Path $buildDir -Force | Out-Null - + Write-Verbose "debianDir: $debianDir" -Verbose Write-Verbose "dataDir: $dataDir" -Verbose Write-Verbose "buildDir: $buildDir" -Verbose - + # Use cp to preserve symlinks Start-NativeExecution { cp -a $debianDir "$buildDir/DEBIAN" } Start-NativeExecution { cp -a $dataDir/* $buildDir } @@ -2017,14 +2019,14 @@ function New-MacOSPackage $linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent) New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null $finalLinkPath = Join-Path $pkgRoot $link.Destination - + Write-Verbose "Creating symlink at $finalLinkPath" -Verbose - + # Remove if exists if (Test-Path $finalLinkPath) { Remove-Item $finalLinkPath -Force } - + # Get the target of the original symlink and recreate it in the package root if (Test-Path $link.Source) { $linkTarget = (Get-Item $link.Source).Target @@ -2050,10 +2052,10 @@ function New-MacOSPackage # Build the component package using pkgbuild $pkgIdentifier = Get-MacOSPackageId -IsPreview:($Name -like '*-preview') - + if ($PSCmdlet.ShouldProcess("Build component package with pkgbuild")) { Write-Log "Running pkgbuild to create component package..." - + Start-NativeExecution -VerboseOutputOnError { pkgbuild --root $pkgRoot ` --identifier $pkgIdentifier ` @@ -2062,7 +2064,7 @@ function New-MacOSPackage --install-location "/" ` $componentPkgPath } - + Write-Verbose "Component package created: $componentPkgPath" -Verbose } @@ -2075,7 +2077,7 @@ function New-MacOSPackage -HostArchitecture $HostArchitecture ` -PackageIdentifier $pkgIdentifier ` -IsPreview:($Name -like '*-preview') - + return $distributionPackage } finally { From 3596ffa9090f2f3dd2f1bdba2fa98172ad7cbcd3 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 10 Nov 2025 11:05:12 -0800 Subject: [PATCH 101/255] Optimize/split windows package signing (#26403) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../chatmodes/cherry-pick-commits.chatmode.md | 78 ++++++ .../build-configuration-guide.instructions.md | 3 +- ...ode-review-branch-strategy.instructions.md | 230 ++++++++++++++++++ ...anch-restore-phase-pattern.instructions.md | 83 +++++++ ...anch-signing-configuration.instructions.md | 195 +++++++++++++++ .pipelines/MSIXBundle-vPack-Official.yml | 1 - ...werShell-Coordinated_Packages-Official.yml | 1 - .pipelines/PowerShell-Packages-Official.yml | 62 +++-- .pipelines/PowerShell-vPack-Official.yml | 1 - .pipelines/templates/SetVersionVariables.yml | 59 ++--- .pipelines/templates/checkAzureContainer.yml | 3 +- .pipelines/templates/cloneToOfficialPath.yml | 18 +- .pipelines/templates/compliance/apiscan.yml | 3 +- .pipelines/templates/install-dotnet.yml | 7 +- .pipelines/templates/linux-package-build.yml | 3 +- .pipelines/templates/mac-package-build.yml | 3 +- .pipelines/templates/nupkg.yml | 3 +- .pipelines/templates/package-create-msix.yml | 6 +- .../windows/package.yml} | 149 +++--------- .../templates/packaging/windows/sign.yml | 216 ++++++++++++++++ .../templates/release-MakeBlobPublic.yml | 6 +- .../templates/release-checkout-pwsh-repo.yml | 13 - .../templates/release-download-packages.yml | 122 ---------- .pipelines/templates/release-install-pwsh.yml | 34 --- .pipelines/templates/set-reporoot.yml | 35 +++ .pipelines/templates/shouldSign.yml | 7 +- .pipelines/templates/uploadToAzure.yml | 3 +- 27 files changed, 969 insertions(+), 375 deletions(-) create mode 100644 .github/chatmodes/cherry-pick-commits.chatmode.md create mode 100644 .github/instructions/code-review-branch-strategy.instructions.md create mode 100644 .github/instructions/onebranch-restore-phase-pattern.instructions.md create mode 100644 .github/instructions/onebranch-signing-configuration.instructions.md rename .pipelines/templates/{windows-package-build.yml => packaging/windows/package.yml} (56%) create mode 100644 .pipelines/templates/packaging/windows/sign.yml delete mode 100644 .pipelines/templates/release-checkout-pwsh-repo.yml delete mode 100644 .pipelines/templates/release-download-packages.yml delete mode 100644 .pipelines/templates/release-install-pwsh.yml create mode 100644 .pipelines/templates/set-reporoot.yml diff --git a/.github/chatmodes/cherry-pick-commits.chatmode.md b/.github/chatmodes/cherry-pick-commits.chatmode.md new file mode 100644 index 00000000000..826ab11d56c --- /dev/null +++ b/.github/chatmodes/cherry-pick-commits.chatmode.md @@ -0,0 +1,78 @@ +# Cherry-Pick Commits Between Branches + +Cherry-pick recent commits from a source branch to a target branch without switching branches. + +## Instructions for Copilot + +1. **Confirm branches with the user** + - Ask the user to confirm the source and target branches + - If different branches are needed, update the configuration + +2. **Identify unique commits** + - Run: `git log .. --oneline --reverse` + - **IMPORTANT**: The commit count may be misleading if branches diverged from different base commits + - Compare the LAST few commits from each branch to identify actual missing commits: + - `git log --oneline -10` + - `git log --oneline -10` + - Look for commits with the same message but different SHAs (rebased commits) + - Show the user ONLY the truly missing commits (usually just the most recent ones) + +3. **Confirm with user before proceeding** + - If the commit count seems unusually high (e.g., 400+), STOP and verify semantically + - Ask: "I found X commits to cherry-pick. Shall I proceed?" + - If there are many commits, warn that this may take time + +4. **Execute the cherry-pick** + - Ensure the target branch is checked out first + - Run: `git cherry-pick ` for single commits + - Or: `git cherry-pick ` for multiple commits + - Apply commits in chronological order (oldest first) + +5. **Handle any issues** + - If conflicts occur, pause and ask user for guidance + - If empty commits occur, automatically skip with `git cherry-pick --skip` + +6. **Verify and report results** + - Run: `git log - --oneline` + - Show the user the newly applied commits + - Confirm the branch is now ahead by X commits + +## Key Git Commands + +```bash +# Find unique commits (may show full divergence if branches were rebased) +git log .. --oneline --reverse + +# Compare recent commits on each branch (more reliable for rebased branches) +git log --oneline -10 +git log --oneline -10 + +# Cherry-pick specific commits (when target is checked out) +git cherry-pick +git cherry-pick + +# Skip empty commits +git cherry-pick --skip + +# Verify result +git log - --oneline +``` + +## Common Scenarios + +- **Empty commits**: Automatically skip with `git cherry-pick --skip` +- **Conflicts**: Stop, show files with conflicts, ask user to resolve +- **Many commits**: Warn user and confirm before proceeding +- **Already applied**: These will result in empty commits that should be skipped +- **Diverged branches**: If branches diverged (rebased), `git log` may show the entire history difference + - The actual missing commits are usually only the most recent ones + - Compare commit messages from recent history on both branches + - Cherry-pick only commits that are semantically missing + +## Workflow Style + +Use an interactive, step-by-step approach: +- Show output from each command +- Ask for confirmation before major actions +- Provide clear status updates +- Handle errors gracefully with user guidance diff --git a/.github/instructions/build-configuration-guide.instructions.md b/.github/instructions/build-configuration-guide.instructions.md index 56844777d95..d0384f4f307 100644 --- a/.github/instructions/build-configuration-guide.instructions.md +++ b/.github/instructions/build-configuration-guide.instructions.md @@ -4,6 +4,7 @@ applyTo: - "tools/ci.psm1" - ".github/**/*.yml" - ".github/**/*.yaml" + - ".pipelines/**/*.yml" --- # Build Configuration Guide @@ -121,7 +122,7 @@ The `Switch-PSNugetConfig` function in `build.psm1` manages NuGet package source - **Public**: Uses public feeds (nuget.org and public Azure DevOps feeds) - Required for: CI/CD environments, public builds, packaging - Does not require authentication - + - **Private**: Uses internal PowerShell team feeds - Required for: Internal development with preview packages - Requires authentication credentials diff --git a/.github/instructions/code-review-branch-strategy.instructions.md b/.github/instructions/code-review-branch-strategy.instructions.md new file mode 100644 index 00000000000..191a677b912 --- /dev/null +++ b/.github/instructions/code-review-branch-strategy.instructions.md @@ -0,0 +1,230 @@ +--- +applyTo: "**/*" +--- + +# Code Review Branch Strategy Guide + +This guide helps GitHub Copilot provide appropriate feedback when reviewing code changes, particularly distinguishing between issues that should be fixed in the current branch versus the default branch. + +## Purpose + +When reviewing pull requests, especially those targeting release branches, it's important to identify whether an issue should be fixed in: +- **The current PR/branch** - Release-specific fixes or backports +- **The default branch first** - General bugs that exist in the main codebase + +## Branch Types and Fix Strategy + +### Release Branches (e.g., `release/v7.5`, `release/v7.4`) + +**Purpose:** Contain release-specific changes and critical backports + +**Should contain:** +- Release-specific configuration changes +- Critical bug fixes that are backported from the default branch +- Release packaging/versioning adjustments + +**Should NOT contain:** +- New general bug fixes that haven't been fixed in the default branch +- Refactoring or improvements that apply to the main codebase +- Workarounds for issues that exist in the default branch + +### Default/Main Branch (e.g., `master`, `main`) + +**Purpose:** Primary development branch for all ongoing work + +**Should contain:** +- All general bug fixes +- New features and improvements +- Refactoring and code quality improvements +- Fixes that will later be backported to release branches + +## Identifying Issues That Belong in the Default Branch + +When reviewing a PR targeting a release branch, look for these indicators that suggest the fix should be in the default branch first: + +### 1. The Root Cause Exists in Default Branch + +If the underlying issue exists in the default branch's code, it should be fixed there first. + +**Example:** +```yaml +# PR changes this in release/v7.5: +- $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json ++ $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json +``` + +**Analysis:** If `$repoRoot` is undefined because the template doesn't include its dependencies in BOTH the release branch AND the default branch, the fix should address the root cause in the default branch first. + +### 2. The Fix is a Workaround Rather Than a Proper Solution + +If the change introduces a workaround (hardcoded paths, special cases) rather than fixing the underlying design issue, it likely belongs in the default branch as a proper fix. + +**Example:** +- Using hardcoded paths instead of fixing variable initialization +- Adding special cases instead of fixing the logic +- Duplicating code instead of fixing shared dependencies + +### 3. The Issue Affects General Functionality + +If the issue affects general functionality not specific to a release, it should be fixed in the default branch. + +**Example:** +- Template dependencies that affect all pipelines +- Shared utility functions +- Common configuration issues + +## Providing Code Review Feedback + +### For Issues in the Current Branch + +When an issue is specific to the current branch or is a legitimate fix for the branch being targeted, **use the default code review feedback format** without any special branch-strategy commentary. + +### For Issues That Belong in the Default Branch + +1. **Provide the code review feedback** +2. **Explain why it should be fixed in the default branch** +3. **Provide an issue template** in markdown format + +**Example:** + +```markdown +The `channelSelection.yml` template relies on `$repoRoot` being set by `SetVersionVariables.yml`, but doesn't declare this dependency. This issue exists in both the release branch and the default branch. + +**This should be fixed in the default branch first**, then backported if needed. The proper fix is to ensure template dependencies are correctly declared, rather than using hardcoded paths as a workaround. + +--- + +**Suggested Issue for Default Branch:** + +### Issue Title +`channelSelection.yml` template missing dependency on `SetVersionVariables.yml` + +### Description +The `channelSelection.yml` template uses the `$repoRoot` variable but doesn't ensure it's set beforehand by including `SetVersionVariables.yml`. + +**Current State:** +- `channelSelection.yml` expects `$repoRoot` to be available +- Not all pipelines that use `channelSelection.yml` include `SetVersionVariables.yml` first +- This creates an implicit dependency that's not enforced + +**Expected State:** +Either: +1. `channelSelection.yml` should include `SetVersionVariables.yml` as a dependency, OR +2. `channelSelection.yml` should be refactored to not depend on `$repoRoot`, OR +3. Pipelines using `channelSelection.yml` should explicitly include `SetVersionVariables.yml` first + +**Files Affected:** +- `.pipelines/templates/channelSelection.yml` +- `.pipelines/templates/package-create-msix.yml` +- `.pipelines/templates/release-SetTagAndChangelog.yml` + +**Priority:** Medium +**Labels:** `Issue-Bug`, `Area-Build`, `Area-Pipeline` +``` + +## Issue Template Format + +When creating an issue template for the default branch, use this structure: + +```markdown +### Issue Title +[Clear, concise description of the problem] + +### Description +[Detailed explanation of the issue] + +**Current State:** +- [What's happening now] +- [Why it's problematic] + +**Expected State:** +- [What should happen] +- [Proposed solution(s)] + +**Files Affected:** +- [List of files] + +**Priority:** [Low/Medium/High/Critical] +**Labels:** [Suggested labels like `Issue-Bug`, `Area-*`] + +**Additional Context:** +[Any additional information, links to related issues, etc.] +``` + +## Common Scenarios + +### Scenario 1: Template Dependency Issues + +**Indicators:** +- Missing template includes +- Undefined variables from other templates +- Assumptions about pipeline execution order + +**Action:** Suggest fixing template dependencies in the default branch. + +### Scenario 2: Hardcoded Values + +**Indicators:** +- Hardcoded paths replacing variables +- Environment-specific values in shared code +- Magic strings or numbers + +**Action:** Suggest proper variable/parameter usage in the default branch. + +### Scenario 3: Logic Errors + +**Indicators:** +- Incorrect conditional logic +- Missing error handling +- Race conditions + +**Action:** Suggest fixing the logic in the default branch unless it's release-specific. + +### Scenario 4: Legitimate Release Branch Fixes + +**Indicators:** +- Version-specific configuration +- Release packaging changes +- Backport of already-fixed default branch issue + +**Action:** Provide normal code review feedback for the current PR. + +## Best Practices + +1. **Always check if the issue exists in the default branch** before suggesting a release-branch-only fix +2. **Prefer fixing root causes over workarounds** +3. **Provide clear rationale** for why a fix belongs in the default branch +4. **Include actionable issue templates** so users can easily create issues +5. **Be helpful, not blocking** - provide the feedback even if you can't enforce where it's fixed + +## Examples of Good vs. Bad Approaches + +### ❌ Bad: Workaround in Release Branch Only + +```yaml +# In release/v7.5 only +- pwsh: | + $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw +``` + +**Why bad:** Hardcodes path to work around missing `$repoRoot`, doesn't fix the default branch. + +### ✅ Good: Fix in Default Branch, Then Backport + +```yaml +# In default branch first +- template: SetVersionVariables.yml@self # Ensures $repoRoot is set +- template: channelSelection.yml@self # Now can use $repoRoot +``` + +**Why good:** Fixes the root cause by ensuring dependencies are declared, then backport to release if needed. + +## When in Doubt + +If you're unsure whether an issue should be fixed in the current branch or the default branch, ask yourself: + +1. Does this issue exist in the default branch? +2. Is this a workaround or a proper fix? +3. Will other branches/releases benefit from this fix? + +If the answer to any of these is "yes," suggest fixing it in the default branch first. diff --git a/.github/instructions/onebranch-restore-phase-pattern.instructions.md b/.github/instructions/onebranch-restore-phase-pattern.instructions.md new file mode 100644 index 00000000000..0945bb47c0b --- /dev/null +++ b/.github/instructions/onebranch-restore-phase-pattern.instructions.md @@ -0,0 +1,83 @@ +--- +applyTo: ".pipelines/**/*.{yml,yaml}" +--- + +# OneBranch Restore Phase Pattern + +## Overview +When steps need to run in the OneBranch restore phase (before the main build phase), the `ob_restore_phase` environment variable must be set in the `env:` block of **each individual step**. + +## Pattern + +### ✅ Correct (Working Pattern) +```yaml +parameters: +- name: "ob_restore_phase" + type: boolean + default: true # or false if you don't want restore phase + +steps: +- powershell: | + # script content + displayName: 'Step Name' + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} +``` + +The key is to: +1. Define `ob_restore_phase` as a **boolean** parameter +2. Set `ob_restore_phase: ${{ parameters.ob_restore_phase }}` directly in each step's `env:` block +3. Pass `true` to run in restore phase, `false` to run in normal build phase + +### ❌ Incorrect (Does Not Work) +```yaml +steps: +- powershell: | + # script content + displayName: 'Step Name' + ${{ if eq(parameters.useRestorePhase, 'yes') }}: + env: + ob_restore_phase: true +``` + +Using conditionals at the same indentation level as `env:` causes only the first step to execute in restore phase. + +## Parameters + +Templates using this pattern should accept an `ob_restore_phase` boolean parameter: + +```yaml +parameters: +- name: "ob_restore_phase" + type: boolean + default: true # Set to true to run in restore phase by default +``` + +## Reference Examples + +Working examples of this pattern can be found in: +- `.pipelines/templates/insert-nuget-config-azfeed.yml` - Demonstrates the correct pattern +- `.pipelines/templates/SetVersionVariables.yml` - Updated to use this pattern + +## Why This Matters + +The restore phase in OneBranch pipelines runs before signing and other build operations. Steps that need to: +- Set environment variables for the entire build +- Configure authentication +- Prepare the repository structure + +Must run in the restore phase to be available when subsequent stages execute. + +## Common Use Cases + +- Setting `REPOROOT` variable +- Configuring NuGet feeds with authentication +- Setting version variables +- Repository preparation and validation + +## Troubleshooting + +If only the first step in your template is running in restore phase: +1. Check that `env:` block exists for **each step** +2. Verify the conditional `${{ if ... }}:` is **inside** the `env:` block +3. Confirm indentation is correct (conditional is indented under `env:`) diff --git a/.github/instructions/onebranch-signing-configuration.instructions.md b/.github/instructions/onebranch-signing-configuration.instructions.md new file mode 100644 index 00000000000..747fcaffdd6 --- /dev/null +++ b/.github/instructions/onebranch-signing-configuration.instructions.md @@ -0,0 +1,195 @@ +--- +applyTo: + - ".pipelines/**/*.yml" + - ".pipelines/**/*.yaml" +--- + +# OneBranch Signing Configuration + +This guide explains how to configure OneBranch signing variables in Azure Pipeline jobs, particularly when signing is not required. + +## Purpose + +OneBranch pipelines include signing infrastructure by default. For build-only jobs where signing happens in a separate stage, you should disable signing setup to improve performance and avoid unnecessary overhead. + +## Disable Signing for Build-Only Jobs + +When a job does not perform signing (e.g., it only builds artifacts that will be signed in a later stage), disable both signing setup and code sign validation: + +```yaml +variables: + - name: ob_signing_setup_enabled + value: false # Disable signing setup - this is a build-only stage + - name: ob_sdl_codeSignValidation_enabled + value: false # Skip signing validation in build-only stage +``` + +### Why Disable These Variables? + +**`ob_signing_setup_enabled: false`** +- Prevents OneBranch from setting up the signing infrastructure +- Reduces job startup time +- Avoids unnecessary credential validation +- Only disable when the job will NOT sign any artifacts + +**`ob_sdl_codeSignValidation_enabled: false`** +- Skips validation that checks if files are properly signed +- Appropriate for build stages where artifacts are unsigned +- Must be enabled in signing/release stages to validate signatures + +## Common Patterns + +### Build-Only Job (No Signing) + +```yaml +jobs: +- job: build_artifacts + variables: + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_codeSignValidation_enabled + value: false + steps: + - checkout: self + - pwsh: | + # Build unsigned artifacts + Start-PSBuild +``` + +### Signing Job + +```yaml +jobs: +- job: sign_artifacts + variables: + - name: ob_signing_setup_enabled + value: true + - name: ob_sdl_codeSignValidation_enabled + value: true + steps: + - checkout: self + env: + ob_restore_phase: true # Steps before first signing operation + - pwsh: | + # Prepare artifacts for signing + env: + ob_restore_phase: true # Steps before first signing operation + - task: onebranch.pipeline.signing@1 + displayName: 'Sign artifacts' + # Signing step runs in build phase (no ob_restore_phase) + - pwsh: | + # Post-signing validation + # Post-signing steps run in build phase (no ob_restore_phase) +``` + +## Restore Phase Usage with Signing + +**The restore phase (`ob_restore_phase: true`) should only be used in jobs that perform signing operations.** It separates preparation steps from the actual signing and build steps. + +### When to Use Restore Phase + +Use `ob_restore_phase: true` **only** in jobs where `ob_signing_setup_enabled: true`: + +```yaml +jobs: +- job: sign_artifacts + variables: + - name: ob_signing_setup_enabled + value: true # Signing enabled + steps: + # Steps BEFORE first signing operation: use restore phase + - checkout: self + env: + ob_restore_phase: true + - template: prepare-for-signing.yml + parameters: + ob_restore_phase: true + + # SIGNING STEP: runs in build phase (no ob_restore_phase) + - task: onebranch.pipeline.signing@1 + displayName: 'Sign artifacts' + + # Steps AFTER signing: run in build phase (no ob_restore_phase) + - pwsh: | + # Validation or packaging +``` + +### When NOT to Use Restore Phase + +**Do not use restore phase in build-only jobs** where `ob_signing_setup_enabled: false`: + +```yaml +jobs: +- job: build_artifacts + variables: + - name: ob_signing_setup_enabled + value: false # No signing + - name: ob_sdl_codeSignValidation_enabled + value: false + steps: + - checkout: self + # NO ob_restore_phase - not needed without signing + - pwsh: | + Start-PSBuild +``` + +**Why?** The restore phase is part of OneBranch's signing infrastructure. Using it without signing enabled adds unnecessary overhead without benefit. + +## Related Variables + +Other OneBranch signing-related variables: + +- `ob_sdl_binskim_enabled`: Controls BinSkim security analysis (can be false in build-only, true in signing stages) + +## Best Practices + +1. **Separate build and signing stages**: Build artifacts in one job, sign in another +2. **Disable signing in build stages**: Improves performance and clarifies intent +3. **Only use restore phase with signing**: The restore phase should only be used in jobs where signing is enabled (`ob_signing_setup_enabled: true`) +4. **Restore phase before first signing step**: All steps before the first signing operation should use `ob_restore_phase: true` +5. **Always validate after signing**: Enable validation in signing stages to catch issues +6. **Document the reason**: Add comments explaining why signing is disabled or why restore phase is used + +## Example: Split Build and Sign Pipeline + +```yaml +stages: + - stage: Build + jobs: + - job: build_windows + variables: + - name: ob_signing_setup_enabled + value: false # Build-only, no signing + - name: ob_sdl_codeSignValidation_enabled + value: false # Artifacts are unsigned + steps: + - template: templates/build-unsigned.yml + + - stage: Sign + dependsOn: Build + jobs: + - job: sign_windows + variables: + - name: ob_signing_setup_enabled + value: true # Enable signing infrastructure + - name: ob_sdl_codeSignValidation_enabled + value: true # Validate signatures + steps: + - template: templates/sign-artifacts.yml +``` + +## Troubleshooting + +**Job fails with signing-related errors but signing is disabled:** +- Verify `ob_signing_setup_enabled: false` is set in variables +- Check that no template is overriding the setting +- Ensure `ob_sdl_codeSignValidation_enabled: false` is also set + +**Signed artifacts fail validation:** +- Confirm `ob_sdl_codeSignValidation_enabled: true` in signing job +- Verify signing actually occurred +- Check certificate configuration + +## Reference + +- PowerShell signing templates: `.pipelines/templates/packaging/windows/sign.yml` diff --git a/.pipelines/MSIXBundle-vPack-Official.yml b/.pipelines/MSIXBundle-vPack-Official.yml index 8e175c5a6bb..997b7c458be 100644 --- a/.pipelines/MSIXBundle-vPack-Official.yml +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -99,7 +99,6 @@ extends: - template: .pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - UseJson: no - pwsh: | Write-Verbose -Verbose 'PowerShell Version: $(version)' diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml index e70b65a9a64..f36eeadf298 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -178,7 +178,6 @@ extends: parameters: ReleaseTagVar: $(ReleaseTagVar) CreateJson: yes - UseJson: no - stage: macos displayName: macOS - build and sign diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml index 9c2dee1c571..8e06be1cc64 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -127,11 +127,13 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - stage: prep + displayName: 'Prep BuildInfo+Az' jobs: - template: /.pipelines/templates/checkAzureContainer.yml@self - stage: mac_package - dependsOn: [prep] + displayName: 'macOS Pkg+Sign' + dependsOn: [] jobs: - template: /.pipelines/templates/mac-package-build.yml@self parameters: @@ -141,35 +143,65 @@ extends: parameters: buildArchitecture: arm64 - - stage: windows_package - dependsOn: [prep] + - stage: windows_package_build + displayName: 'Win Pkg (unsigned)' + dependsOn: [] jobs: - - template: /.pipelines/templates/windows-package-build.yml@self + - template: /.pipelines/templates/packaging/windows/package.yml@self parameters: runtime: x64 - - template: /.pipelines/templates/windows-package-build.yml@self + - template: /.pipelines/templates/packaging/windows/package.yml@self parameters: runtime: arm64 - - template: /.pipelines/templates/windows-package-build.yml@self + - template: /.pipelines/templates/packaging/windows/package.yml@self parameters: runtime: x86 - - template: /.pipelines/templates/windows-package-build.yml@self + - template: /.pipelines/templates/packaging/windows/package.yml@self parameters: runtime: fxdependent - - template: /.pipelines/templates/windows-package-build.yml@self + - template: /.pipelines/templates/packaging/windows/package.yml@self parameters: runtime: fxdependentWinDesktop - - template: /.pipelines/templates/windows-package-build.yml@self + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: minsize + + - stage: windows_package_sign + displayName: 'Win Pkg Sign' + dependsOn: [windows_package_build] + jobs: + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/sign.yml@self parameters: runtime: minsize - stage: linux_package - dependsOn: [prep] + displayName: 'Linux Pkg+Sign' + dependsOn: [] jobs: - template: /.pipelines/templates/linux-package-build.yml@self parameters: @@ -251,17 +283,19 @@ extends: jobName: minSize - stage: nupkg - dependsOn: [prep] + displayName: 'NuGet Pkg+Sign' + dependsOn: [] jobs: - template: /.pipelines/templates/nupkg.yml@self - stage: msixbundle - displayName: 'Create MSIX Bundle' - dependsOn: [windows_package] + displayName: 'MSIX Bundle+Sign' + dependsOn: [windows_package_build] # Only depends on unsigned packages jobs: - template: /.pipelines/templates/package-create-msix.yml@self - stage: upload - dependsOn: [mac_package, windows_package, linux_package, nupkg, msixbundle] + displayName: 'Upload' + dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON jobs: - template: /.pipelines/templates/uploadToAzure.yml@self diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index 7ba92f4eda9..096dfb574a4 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -159,7 +159,6 @@ extends: parameters: ReleaseTagVar: $(ReleaseTagVar) CreateJson: yes - UseJson: no - pwsh: | $version = '$(Version)' diff --git a/.pipelines/templates/SetVersionVariables.yml b/.pipelines/templates/SetVersionVariables.yml index 9f692373f6c..30ed1704022 100644 --- a/.pipelines/templates/SetVersionVariables.yml +++ b/.pipelines/templates/SetVersionVariables.yml @@ -1,49 +1,18 @@ parameters: - ReleaseTagVar: v6.2.0 - ReleaseTagVarName: ReleaseTagVar - CreateJson: 'no' - UseJson: 'yes' +- name: ReleaseTagVar + default: v6.2.0 +- name: ReleaseTagVarName + default: ReleaseTagVar +- name: CreateJson + default: 'no' +- name: ob_restore_phase + type: boolean + default: true steps: -- ${{ if eq(parameters['UseJson'],'yes') }}: - - task: DownloadBuildArtifacts@0 - inputs: - artifactName: 'drop_prep_SetVars' - itemPattern: '*.json' - downloadPath: '$(System.ArtifactsDirectory)' - displayName: Download Build Info Json - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - -- powershell: | - $path = "./build.psm1" - if($env:REPOROOT){ - Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose - exit 0 - } - if(Test-Path -Path $path) - { - Write-Verbose "reporoot detected at: ." -Verbose - $repoRoot = '.' - } - else{ - $path = "./PowerShell/build.psm1" - if(Test-Path -Path $path) - { - Write-Verbose "reporoot detect at: ./PowerShell" -Verbose - $repoRoot = './PowerShell' - } - } - if($repoRoot) { - $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - } else { - Write-Verbose -Verbose "repo not found" - } - displayName: 'Set repo Root' - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue +- template: set-reporoot.yml@self + parameters: + ob_restore_phase: ${{ parameters.ob_restore_phase }} - powershell: | $createJson = ("${{ parameters.CreateJson }}" -ne "no") @@ -69,11 +38,11 @@ steps: Write-Host "##$vstsCommandString" displayName: 'Set ${{ parameters.ReleaseTagVarName }} and other version Variables' env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + ob_restore_phase: ${{ parameters.ob_restore_phase }} - powershell: | Get-ChildItem -Path Env: | Out-String -Width 150 displayName: Capture environment condition: succeededOrFailed() env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/checkAzureContainer.yml b/.pipelines/templates/checkAzureContainer.yml index a6a86214d07..3e383d2c572 100644 --- a/.pipelines/templates/checkAzureContainer.yml +++ b/.pipelines/templates/checkAzureContainer.yml @@ -3,6 +3,8 @@ jobs: variables: - group: Azure Blob variable group - group: AzureBlobServiceConnection + - name: ob_artifactBaseName + value: BuildInfoJson - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' - name: ob_sdl_sbom_enabled @@ -29,7 +31,6 @@ jobs: parameters: ReleaseTagVar: $(ReleaseTagVar) CreateJson: yes - UseJson: no - template: /.pipelines/templates/cloneToOfficialPath.yml@self diff --git a/.pipelines/templates/cloneToOfficialPath.yml b/.pipelines/templates/cloneToOfficialPath.yml index 844d8b8028d..b060c713683 100644 --- a/.pipelines/templates/cloneToOfficialPath.yml +++ b/.pipelines/templates/cloneToOfficialPath.yml @@ -1,5 +1,9 @@ parameters: - nativePathRoot: '' +- name: nativePathRoot + default: '' +- name: ob_restore_phase + type: boolean + default: true steps: - powershell: | @@ -12,8 +16,16 @@ steps: else { Write-Verbose -Verbose -Message "No cleanup required." } - git clone --quiet $env:REPOROOT $nativePath + # REPOROOT must be set by the pipeline - this is where the repository was checked out + $sourceDir = $env:REPOROOT + if (-not $sourceDir) { throw "REPOROOT environment variable is not set. This step depends on REPOROOT being configured in the pipeline." } + + $buildModulePath = Join-Path $sourceDir "build.psm1" + if (-not (Test-Path $buildModulePath)) { throw "build.psm1 not found at: $buildModulePath. REPOROOT must point to the PowerShell repository root." } + + Write-Verbose -Verbose -Message "Cloning from: $sourceDir to $nativePath" + git clone --quiet $sourceDir $nativePath displayName: Clone PowerShell Repo to /PowerShell errorActionPreference: silentlycontinue env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/compliance/apiscan.yml b/.pipelines/templates/compliance/apiscan.yml index 817d5ab777f..5809af8e28c 100644 --- a/.pipelines/templates/compliance/apiscan.yml +++ b/.pipelines/templates/compliance/apiscan.yml @@ -50,8 +50,7 @@ jobs: - template: ../SetVersionVariables.yml parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: ../insert-nuget-config-azfeed.yml parameters: diff --git a/.pipelines/templates/install-dotnet.yml b/.pipelines/templates/install-dotnet.yml index c2a2cfebeca..464e13d1047 100644 --- a/.pipelines/templates/install-dotnet.yml +++ b/.pipelines/templates/install-dotnet.yml @@ -1,3 +1,8 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + steps: - pwsh: | if (-not (Test-Path '$(RepoRoot)')) { @@ -15,5 +20,5 @@ steps: displayName: 'Install dotnet SDK' workingDirectory: $(RepoRoot) env: - ob_restore_phase: true + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml index 68fa46690c2..b1c170eaed5 100644 --- a/.pipelines/templates/linux-package-build.yml +++ b/.pipelines/templates/linux-package-build.yml @@ -54,8 +54,7 @@ jobs: - template: SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: shouldSign.yml diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index d429be91284..1abb1722398 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -52,8 +52,7 @@ jobs: - template: SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: shouldSign.yml diff --git a/.pipelines/templates/nupkg.yml b/.pipelines/templates/nupkg.yml index 4756c99a9d3..3a2aa4f3172 100644 --- a/.pipelines/templates/nupkg.yml +++ b/.pipelines/templates/nupkg.yml @@ -41,8 +41,7 @@ jobs: - template: SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: shouldSign.yml diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index f45f3718b04..ed0217b2940 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -26,7 +26,7 @@ jobs: - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' - artifact: drop_windows_package_package_win_arm64 + artifact: drop_windows_package_arm64 itemPattern: | **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' @@ -35,7 +35,7 @@ jobs: - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' - artifact: drop_windows_package_package_win_x64 + artifact: drop_windows_package_x64 itemPattern: | **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' @@ -44,7 +44,7 @@ jobs: - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' - artifact: drop_windows_package_package_win_x86 + artifact: drop_windows_package_x86 itemPattern: | **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' diff --git a/.pipelines/templates/windows-package-build.yml b/.pipelines/templates/packaging/windows/package.yml similarity index 56% rename from .pipelines/templates/windows-package-build.yml rename to .pipelines/templates/packaging/windows/package.yml index bc23bfc659e..9a8be4b18fe 100644 --- a/.pipelines/templates/windows-package-build.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -2,8 +2,8 @@ parameters: runtime: x64 jobs: -- job: package_win_${{ parameters.runtime }} - displayName: Package Windows ${{ parameters.runtime }} +- job: build_win_${{ parameters.runtime }} + displayName: Build Windows Packages ${{ parameters.runtime }} condition: succeeded() pool: type: windows @@ -11,6 +11,12 @@ jobs: variables: - name: runCodesignValidationInjection value: false + - name: ob_sdl_codeSignValidation_enabled + value: false # Skip signing validation in build-only stage + - name: ob_signing_setup_enabled + value: false # Disable signing setup - this is a build-only stage, signing happens in separate stage + - name: ob_artifactBaseName + value: drop_windows_package_${{ parameters.runtime }} - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel @@ -22,7 +28,7 @@ jobs: - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)\ONEBRANCH_ARTIFACT' - name: ob_sdl_binskim_enabled - value: true + value: false # Disable for build-only, enable in signing stage - name: ob_sdl_tsa_configFile value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - name: ob_sdl_credscan_suppressionsFile @@ -34,40 +40,35 @@ jobs: steps: - checkout: self clean: true - env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - pwsh: | Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture environment - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - - template: SetVersionVariables.yml@self + - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no + ob_restore_phase: false - - template: shouldSign.yml + - template: /.pipelines/templates/shouldSign.yml@self + parameters: + ob_restore_phase: false - - template: cloneToOfficialPath.yml + - template: /.pipelines/templates/cloneToOfficialPath.yml@self parameters: nativePathRoot: '$(Agent.TempDirectory)' + ob_restore_phase: false - download: CoOrdinatedBuildPipeline artifact: drop_windows_build_windows_${{ parameters.runtime }}_release displayName: Download signed artifacts condition: ${{ ne(parameters.runtime, 'minSize') }} - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - download: CoOrdinatedBuildPipeline artifact: drop_windows_build_windows_x64_${{ parameters.runtime }} displayName: Download minsize signed artifacts condition: ${{ eq(parameters.runtime, 'minSize') }} - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - pwsh: | Write-Verbose -Verbose "signed artifacts" @@ -75,18 +76,10 @@ jobs: displayName: 'Capture Downloaded Artifacts' # Diagnostics is not critical it passes every time it runs continueOnError: true - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - template: /.pipelines/templates/install-dotnet.yml@self - - - pwsh: | - $msixUrl = '$(makeappUrl)' - Invoke-RestMethod -Uri $msixUrl -OutFile '$(Pipeline.Workspace)\makeappx.zip' - Expand-Archive '$(Pipeline.Workspace)\makeappx.zip' -destination '\' -Force - displayName: Install packaging tools - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + parameters: + ob_restore_phase: false - pwsh: | $runtime = '$(Runtime)' @@ -168,94 +161,19 @@ jobs: Start-PSPackage -Type $packageTypes -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS - displayName: 'Package ${{ parameters.buildArchitecture}}' + displayName: 'Build Packages (Unsigned)' env: __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - - - task: onebranch.pipeline.signing@1 - displayName: Sign MSI packages - inputs: - command: 'sign' - signing_profile: external_distribution - files_to_sign: '**\*.msi' - search_root: '$(Pipeline.Workspace)' - - - pwsh: | - $runtime = '$(Runtime)' - Write-Verbose -Verbose "runtime = '$(Runtime)'" - - $repoRoot = "$env:REPOROOT" - Import-Module "$repoRoot\build.psm1" - Import-Module "$repoRoot\tools\packaging" - - $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') - - if ($runtime -in $noExeRuntimes) { - Write-Verbose -Verbose "No EXE generated for $runtime" - return - } - - $version = '$(Version)' - - $msiLocation = Get-ChildItem -Path $(Pipeline.Workspace) -Recurse -Filter "powershell-*$runtime.msi" | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiLocation: $msiLocation" - - Set-Location $repoRoot - - $exePath = New-ExePackage -ProductVersion $version -ProductTargetArchitecture $runtime -MsiLocationPath $msiLocation - Write-Verbose -Verbose "setting vso[task.setvariable variable=exePath]$exePath" - Write-Host "##vso[task.setvariable variable=exePath]$exePath" - Write-Verbose -Verbose "exePath: $exePath" - - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - Expand-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime - displayName: 'Make exe and expand package' - - - task: onebranch.pipeline.signing@1 - displayName: Sign exe engine - inputs: - command: 'sign' - signing_profile: $(msft_3rd_party_cert_id) - files_to_sign: '$(System.ArtifactsDirectory)\unsignedEngine\*.exe' - search_root: '$(Pipeline.Workspace)' - - - pwsh: | - $runtime = '$(Runtime)' - Write-Verbose -Verbose "runtime = '$(Runtime)'" - $repoRoot = "$env:REPOROOT" - Import-Module "$repoRoot\build.psm1" - Import-Module "$repoRoot\tools\packaging" - - $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') - - if ($runtime -in $noExeRuntimes) { - Write-Verbose -Verbose "No EXE generated for $runtime" - return - } - - $exePath = '$(exePath)' - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - $enginePath | Get-AuthenticodeSignature | out-string | Write-Verbose -verbose - Compress-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime - displayName: Compress signed exe package - - - task: onebranch.pipeline.signing@1 - displayName: Sign exe packages - inputs: - command: 'sign' - signing_profile: external_distribution - files_to_sign: '**\*.exe' - search_root: '$(Pipeline.Workspace)' + # Copy unsigned packages to output directory - pwsh: | $runtime = '$(Runtime)' Write-Verbose -Verbose "runtime = '$(Runtime)'" $packageTypes = switch ($runtime) { - 'x64' { @('msi', 'zip', 'msix', 'exe') } - 'x86' { @('msi', 'zip', 'msix', 'exe') } - 'arm64' { @('msi', 'zip', 'msix', 'exe') } + 'x64' { @('msi', 'zip', 'msix') } + 'x86' { @('msi', 'zip', 'msix') } + 'arm64' { @('msi', 'zip', 'msix') } 'fxdependent' { 'fxdependent' } 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } 'minsize' { 'min-size' } @@ -268,34 +186,25 @@ jobs: if ($packageTypes -contains 'msi') { $msiPkgNameFilter = "powershell-*.msi" $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiPkgPath: $msiPkgPath" - Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose - } - - if ($packageTypes -contains 'exe') { - $msiPkgNameFilter = "powershell-*.exe" - $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiPkgPath: $msiPkgPath" + Write-Verbose -Verbose "unsigned msiPkgPath: $msiPkgPath" Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose } if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { $zipPkgNameFilter = "powershell-*.zip" $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "zipPkgPath: $zipPkgPath" + Write-Verbose -Verbose "unsigned zipPkgPath: $zipPkgPath" Copy-Item -Path $zipPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose } if ($packageTypes -contains 'msix') { $msixPkgNameFilter = "powershell-*.msix" $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msixPkgPath: $msixPkgPath" + Write-Verbose -Verbose "unsigned msixPkgPath: $msixPkgPath" Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose } - displayName: Copy to output directory + displayName: Copy unsigned packages to output directory - pwsh: | Get-ChildItem -Path $(ob_outputDirectory) -Recurse - displayName: 'List artifacts' - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + displayName: 'List unsigned artifacts' diff --git a/.pipelines/templates/packaging/windows/sign.yml b/.pipelines/templates/packaging/windows/sign.yml new file mode 100644 index 00000000000..4a095ba7694 --- /dev/null +++ b/.pipelines/templates/packaging/windows/sign.yml @@ -0,0 +1,216 @@ +parameters: + runtime: x64 + +jobs: +- job: sign_win_${{ parameters.runtime }} + displayName: Sign Windows Packages ${{ parameters.runtime }} + condition: succeeded() + pool: + type: windows + + variables: + - name: runCodesignValidationInjection + value: false + - name: ob_artifactBaseName + value: drop_windows_package_package_win_${{ parameters.runtime }} + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)\ONEBRANCH_ARTIFACT' + - name: ob_sdl_binskim_enabled + value: true + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: Runtime + value: ${{ parameters.runtime }} + - group: msixTools + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no + + - template: /.pipelines/templates/shouldSign.yml@self + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + # Download unsigned packages from the build stage + - download: current + artifact: drop_windows_package_${{ parameters.runtime }} + displayName: Download unsigned packages + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Downloaded unsigned artifacts:" + Get-ChildItem "$(Pipeline.Workspace)\drop_windows_package_${{ parameters.runtime }}" -Recurse + displayName: 'Capture Downloaded Unsigned Artifacts' + continueOnError: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/install-dotnet.yml@self + + # Import build.psm1 and bootstrap packaging dependencies (WiX Toolset) + - pwsh: | + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + Write-Verbose -Verbose "Modules imported successfully" + + # Install WiX Toolset for EXE package creation + $isArm64 = '$(Runtime)' -eq 'arm64' + $env:RUNTIME = '$(Runtime)' + Start-PSBootstrap -Scenario Package + displayName: 'Import modules and install WiX Toolset' + env: + ob_restore_phase: true + + # Sign MSI packages + - task: onebranch.pipeline.signing@1 + displayName: Sign MSI packages + inputs: + command: 'sign' + signing_profile: external_distribution + files_to_sign: '**\*.msi' + search_root: '$(Pipeline.Workspace)' + + # Create EXE package from signed MSI (for x64, x86, arm64 only) + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + + $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') + + if ($runtime -in $noExeRuntimes) { + Write-Verbose -Verbose "No EXE generated for $runtime" + return + } + + $version = '$(Version)' + + $msiLocation = Get-ChildItem -Path $(Pipeline.Workspace) -Recurse -Filter "powershell-*$runtime.msi" | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "msiLocation: $msiLocation" + + Set-Location $repoRoot + + $exePath = New-ExePackage -ProductVersion $version -ProductTargetArchitecture $runtime -MsiLocationPath $msiLocation + Write-Verbose -Verbose "setting vso[task.setvariable variable=exePath]$exePath" + Write-Host "##vso[task.setvariable variable=exePath]$exePath" + Write-Verbose -Verbose "exePath: $exePath" + + $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe + Expand-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime + displayName: 'Make exe and expand package' + + # Sign EXE engine + - task: onebranch.pipeline.signing@1 + displayName: Sign exe engine + inputs: + command: 'sign' + signing_profile: $(msft_3rd_party_cert_id) + files_to_sign: '$(System.ArtifactsDirectory)\unsignedEngine\*.exe' + search_root: '$(Pipeline.Workspace)' + + # Compress signed EXE engine back into package + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + + $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') + + if ($runtime -in $noExeRuntimes) { + Write-Verbose -Verbose "No EXE generated for $runtime" + return + } + + $exePath = '$(exePath)' + $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe + $enginePath | Get-AuthenticodeSignature | out-string | Write-Verbose -verbose + Compress-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime + displayName: Compress signed exe package + + # Sign final EXE packages + - task: onebranch.pipeline.signing@1 + displayName: Sign exe packages + inputs: + command: 'sign' + signing_profile: external_distribution + files_to_sign: '**\*.exe' + search_root: '$(Pipeline.Workspace)' + + # Copy all signed packages to output directory + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + + $packageTypes = switch ($runtime) { + 'x64' { @('msi', 'zip', 'msix', 'exe') } + 'x86' { @('msi', 'zip', 'msix', 'exe') } + 'arm64' { @('msi', 'zip', 'msix', 'exe') } + 'fxdependent' { 'fxdependent' } + 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } + 'minsize' { 'min-size' } + } + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + if ($packageTypes -contains 'msi') { + $msiPkgNameFilter = "powershell-*.msi" + $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed msiPkgPath: $msiPkgPath" + Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + + if ($packageTypes -contains 'exe') { + $exePkgNameFilter = "powershell-*.exe" + $exePkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $exePkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed exePkgPath: $exePkgPath" + Copy-Item -Path $exePkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + + if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { + $zipPkgNameFilter = "powershell-*.zip" + $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed zipPkgPath: $zipPkgPath" + Copy-Item -Path $zipPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + + if ($packageTypes -contains 'msix') { + $msixPkgNameFilter = "powershell-*.msix" + $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed msixPkgPath: $msixPkgPath" + Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + displayName: Copy signed packages to output directory + + - pwsh: | + Get-ChildItem -Path $(ob_outputDirectory) -Recurse + displayName: 'List signed artifacts' + env: + ob_restore_phase: true diff --git a/.pipelines/templates/release-MakeBlobPublic.yml b/.pipelines/templates/release-MakeBlobPublic.yml index c8f12938d25..758298202a1 100644 --- a/.pipelines/templates/release-MakeBlobPublic.yml +++ b/.pipelines/templates/release-MakeBlobPublic.yml @@ -45,8 +45,7 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - pwsh: | Get-ChildItem Env: @@ -132,8 +131,7 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - pwsh: | Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose diff --git a/.pipelines/templates/release-checkout-pwsh-repo.yml b/.pipelines/templates/release-checkout-pwsh-repo.yml deleted file mode 100644 index 9a7486887a6..00000000000 --- a/.pipelines/templates/release-checkout-pwsh-repo.yml +++ /dev/null @@ -1,13 +0,0 @@ -steps: - - pwsh: | - Write-Verbose -Verbose "Deploy Box Product Pathway Does Not Support the `"checkout`" task" - if ($ENV:BUILD_REASON -eq 'PullRequest') { - throw 'We dont support PRs' - } - - Write-Verbose -Verbose $ENV:BUILD_SOURCEBRANCH - $branchName = $ENV:BUILD_SOURCEBRANCH -replace '^refs/heads/' - Write-Verbose -Verbose "Branch Name: $branchName" - git clone --depth 1 --branch $branchName https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/PowerShell '$(Pipeline.Workspace)/PowerShell' - cd $(Pipeline.Workspace)/PowerShell - displayName: Checkout Powershell Repository diff --git a/.pipelines/templates/release-download-packages.yml b/.pipelines/templates/release-download-packages.yml deleted file mode 100644 index 27a3098d1e1..00000000000 --- a/.pipelines/templates/release-download-packages.yml +++ /dev/null @@ -1,122 +0,0 @@ -jobs: -- job: upload_packages - displayName: Upload packages - condition: succeeded() - pool: - type: windows - variables: - - template: ./variable/release-shared.yml@self - parameters: - REPOROOT: $(Build.SourcesDirectory) - SBOM: true - - steps: - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment variables - - - download: PSPackagesOfficial - artifact: drop_linux_package_deb - displayName: Download linux deb packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_fxdependent - displayName: Download linux fx packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_mariner_arm64 - displayName: Download linux mariner packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_mariner_x64 - displayName: Download linux mariner x64 packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_minSize - displayName: Download linux min packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_rpm - displayName: Download linux rpm packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar - displayName: Download linux tar packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_alpine - displayName: Download linux tar alpine packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_alpine_fxd - displayName: Download linux tar alpine fxd packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_arm - displayName: Download linux tar arm packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_arm64 - displayName: Download linux tar arm 64 packages - - - download: PSPackagesOfficial - artifact: drop_nupkg_build_nupkg - displayName: Download nupkg packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_arm64 - displayName: Download windows arm64 packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_fxdependent - displayName: Download windows fxdependent packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_fxdependentWinDesktop - displayName: Download windows fxdependentWinDesktop packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_minsize - displayName: Download windows minsize packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_x64 - displayName: Download windows x64 packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_x86 - displayName: Download windows x86 packages - - - download: PSPackagesOfficial - artifact: macos-pkgs - displayName: Download macos tar packages - - - download: PSPackagesOfficial - artifact: drop_mac_package_sign_package_macos_arm64 - displayName: Download macos arm packages - - - download: PSPackagesOfficial - artifact: drop_mac_package_sign_package_macos_x64 - displayName: Download macos x64 packages - - - pwsh: | - Get-ChildItem '$(Pipeline.Workspace)/PSPackagesOfficial' -Recurse | Select-Object -ExpandProperty FullName - displayName: 'Capture downloads' - - - pwsh: | - $PackagesPath = '$(Pipeline.Workspace)/PSPackagesOfficial' - Write-Verbose -Verbose "Copying Github Release files in $PackagesPath to use in Release Pipeline" - - Write-Verbose -Verbose "Creating output directory for GitHub Release files: $(ob_outputDirectory)/GitHubPackages" - New-Item -Path $(ob_outputDirectory)/GitHubPackages -ItemType Directory -Force - Get-ChildItem -Path "$PackagesPath/*" -Recurse | - Where-Object { $_.Extension -notin '.msix', '.nupkg' } | - Where-Object { $_.Extension -in '.gz', '.pkg', '.msi', '.zip', '.deb', '.rpm', '.zip' } | - Copy-Item -Destination $(ob_outputDirectory)/GitHubPackages -Recurse -Verbose - - Write-Verbose -Verbose "Creating output directory for NuGet packages: $(ob_outputDirectory)/NuGetPackages" - New-Item -Path $(ob_outputDirectory)/NuGetPackages -ItemType Directory -Force - Get-ChildItem -Path "$PackagesPath/*" -Recurse | - Where-Object { $_.Extension -eq '.nupkg' } | - Copy-Item -Destination $(ob_outputDirectory)/NuGetPackages -Recurse -Verbose - displayName: Copy downloads to Artifacts diff --git a/.pipelines/templates/release-install-pwsh.yml b/.pipelines/templates/release-install-pwsh.yml deleted file mode 100644 index 9d7080a7e78..00000000000 --- a/.pipelines/templates/release-install-pwsh.yml +++ /dev/null @@ -1,34 +0,0 @@ -steps: - - task: PowerShell@2 - inputs: - targetType: inline - script: | - $localInstallerPath = Get-ChildItem -Path "$(Pipeline.Workspace)/GitHubPackages" -Filter '*win-x64.msi' | Select-Object -First 1 -ExpandProperty FullName - if (Test-Path -Path $localInstallerPath) { - Write-Verbose -Verbose "Installer found at $localInstallerPath" - } else { - throw "Installer not found" - } - Write-Verbose -Verbose "Installing PowerShell via msiexec" - Start-Process -FilePath msiexec -ArgumentList "/package $localInstallerPath /quiet REGISTER_MANIFEST=1" -Wait -NoNewWindow - $pwshPath = Get-ChildItem -Directory -Path 'C:\Program Files\PowerShell\7*' | Select-Object -First 1 -ExpandProperty FullName - if (Test-Path -Path $pwshPath) { - Write-Verbose -Verbose "PowerShell installed at $pwshPath" - Write-Verbose -Verbose "Adding pwsh to env:PATH" - Write-Host "##vso[task.prependpath]$pwshPath" - } else { - throw "PowerShell not installed" - } - displayName: Install pwsh 7 - - - task: PowerShell@2 - inputs: - targetType: inline - pwsh: true - script: | - Write-Verbose -Verbose "Pwsh 7 Installed" - Write-Verbose -Verbose "env:Path: " - $env:PATH -split ';' | ForEach-Object { - Write-Verbose -Verbose $_ - } - displayName: Check pwsh 7 installation diff --git a/.pipelines/templates/set-reporoot.yml b/.pipelines/templates/set-reporoot.yml new file mode 100644 index 00000000000..af7983afaa1 --- /dev/null +++ b/.pipelines/templates/set-reporoot.yml @@ -0,0 +1,35 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + +steps: +- pwsh: | + $path = "./build.psm1" + if($env:REPOROOT){ + Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose + exit 0 + } + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ." -Verbose + $repoRoot = '.' + } + else{ + $path = "./PowerShell/build.psm1" + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ./PowerShell" -Verbose + $repoRoot = './PowerShell' + } + } + if($repoRoot) { + $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + } else { + Write-Verbose -Verbose "repo not found" + } + displayName: 'Set repo Root' + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/shouldSign.yml b/.pipelines/templates/shouldSign.yml index 4bac9e1a3ae..551297f3aaa 100644 --- a/.pipelines/templates/shouldSign.yml +++ b/.pipelines/templates/shouldSign.yml @@ -1,3 +1,8 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + steps: - powershell: | $shouldSign = $true @@ -22,4 +27,4 @@ steps: Write-Host "##$vstsCommandString" displayName: 'Set SHOULD_SIGN Variable' env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/uploadToAzure.yml b/.pipelines/templates/uploadToAzure.yml index b330a2eef10..20842de81cc 100644 --- a/.pipelines/templates/uploadToAzure.yml +++ b/.pipelines/templates/uploadToAzure.yml @@ -36,8 +36,7 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: /.pipelines/templates/release-SetReleaseTagandContainerName.yml@self From 1934b837a654ae5063190699d1953f3177713c67 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 10 Nov 2025 13:51:02 -0800 Subject: [PATCH 102/255] Move package validation to package pipeline (#26414) --- .pipelines/PowerShell-Packages-Official.yml | 6 ++++++ .pipelines/PowerShell-Release-Official.yml | 9 +-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml index 8e06be1cc64..afd82af4462 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -299,3 +299,9 @@ extends: dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON jobs: - template: /.pipelines/templates/uploadToAzure.yml@self + + - stage: validatePackages + displayName: 'Validate Packages' + dependsOn: [upload] + jobs: + - template: /.pipelines/templates/release-validate-packagenames.yml@self diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml index ca6b79335dd..868d61ebfd0 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -216,12 +216,6 @@ extends: arm64: 'yes' enableCredScan: false - - stage: validatePackages - displayName: 'Validate Packages' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-packagenames.yml@self - - stage: ManualValidation dependsOn: [] displayName: Manual Validation @@ -272,7 +266,6 @@ extends: dependsOn: - ManualValidation - ReleaseAutomation - - validatePackages - fxdpackages - gbltool - validateSdk @@ -365,7 +358,7 @@ extends: This is typically done by the community 1-2 days after the release. - stage: PublishMsix - dependsOn: + dependsOn: - setReleaseTagAndChangelog - PushGitTagAndMakeDraftPublic displayName: Publish MSIX to store From 8b7e493dd9e2e6d020509ec20c14cd3ba7208e14 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 10 Nov 2025 14:03:27 -0800 Subject: [PATCH 103/255] Add rebuild branch support with conditional MSIX signing (#26415) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ServiceGroupRoot/Shell/Run/Run.ps1 | 6 ++- ...werShell-Coordinated_Packages-Official.yml | 13 ++++- .pipelines/PowerShell-Packages-Official.yml | 2 + .pipelines/templates/channelSelection.yml | 26 ++++++++-- .pipelines/templates/linux-package-build.yml | 18 ++++++- .pipelines/templates/mac-package-build.yml | 18 ++++++- .pipelines/templates/package-create-msix.yml | 48 ++++++++++++++----- .../templates/packaging/windows/package.yml | 18 ++++++- .pipelines/templates/rebuild-branch-check.yml | 17 +++++++ 9 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 .pipelines/templates/rebuild-branch-check.yml diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 index 25a5686b33e..23f91c1bff2 100644 --- a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 +++ b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 @@ -354,6 +354,9 @@ try { $skipPublish = $metadataContent.SkipPublish $lts = $metadataContent.LTS + # Check if this is a rebuild version (e.g., 7.4.13-rebuild.5) + $isRebuild = $releaseVersion -match '-rebuild\.' + if ($releaseVersion.Contains('-')) { $channel = 'preview' $packageNames = @('powershell-preview') @@ -363,7 +366,8 @@ try { $packageNames = @('powershell') } - if ($lts) { + # Only add LTS package if not a rebuild branch + if ($lts -and -not $isRebuild) { $packageNames += @('powershell-lts') } diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml index f36eeadf298..e4de1fe5c21 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -296,9 +296,20 @@ extends: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) + - template: /.pipelines/templates/rebuild-branch-check.yml@self - powershell: | $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't mark as LTS release for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, not marking as LTS release" -Verbose + } + @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" Get-Content "$(Build.StagingDirectory)\release.json" diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml index afd82af4462..18ef7b2d14c 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -293,6 +293,8 @@ extends: dependsOn: [windows_package_build] # Only depends on unsigned packages jobs: - template: /.pipelines/templates/package-create-msix.yml@self + parameters: + OfficialBuild: ${{ parameters.OfficialBuild }} - stage: upload displayName: 'Upload' diff --git a/.pipelines/templates/channelSelection.yml b/.pipelines/templates/channelSelection.yml index 3d1f445c559..9dd0f3fb216 100644 --- a/.pipelines/templates/channelSelection.yml +++ b/.pipelines/templates/channelSelection.yml @@ -2,13 +2,31 @@ steps: - pwsh: | # Determine LTS, Preview, or Stable $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json + $releaseTag = '$(OutputReleaseTag.releaseTag)' + + # Rebuild branches should be treated as preview builds + # NOTE: The following regex is duplicated from rebuild-branch-check.yml. + # This duplication is necessary because channelSelection.yml does not call rebuild-branch-check.yml, + # and is used in contexts where that check may not have run. + # If you update this regex, also update it in rebuild-branch-check.yml to keep them in sync. + $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' + $LTS = $metadata.LTSRelease.Latest $Stable = $metadata.StableRelease.Latest - $isPreview = '$(OutputReleaseTag.releaseTag)' -match '-' + $isPreview = $releaseTag -match '-' - $IsLTS = [bool]$LTS - $IsStable = [bool]$Stable - $IsPreview = [bool]$isPreview + # If this is a rebuild branch, force preview mode and ignore LTS metadata + if ($isRebuildBranch) { + $IsLTS = $false + $IsStable = $false + $IsPreview = $true + Write-Verbose -Message "Rebuild branch detected, forcing Preview channel" -Verbose + } + else { + $IsLTS = [bool]$LTS + $IsStable = [bool]$Stable + $IsPreview = [bool]$isPreview + } $channelVars = @{ IsLTS = $IsLTS diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml index b1c170eaed5..bcf332b3778 100644 --- a/.pipelines/templates/linux-package-build.yml +++ b/.pipelines/templates/linux-package-build.yml @@ -62,6 +62,8 @@ jobs: parameters: nativePathRoot: '$(Agent.TempDirectory)' + - template: rebuild-branch-check.yml@self + - download: CoOrdinatedBuildPipeline artifact: ${{ parameters.unsignedDrop }} displayName: 'Download unsigned artifacts' @@ -139,7 +141,21 @@ jobs: } $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" if ($LTS) { Write-Verbose -Message "LTS Release: $LTS" diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index 1abb1722398..4e7040d4acc 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -60,6 +60,8 @@ jobs: parameters: nativePathRoot: '$(Agent.TempDirectory)' + - template: rebuild-branch-check.yml@self + - download: CoOrdinatedBuildPipeline artifact: macosBinResults-${{ parameters.buildArchitecture }} @@ -103,7 +105,21 @@ jobs: Get-PSOptions | Write-Verbose -Verbose $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" if ($LTS) { Write-Verbose -Message "LTS Release: $LTS" diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index ed0217b2940..2b32aa4d0bb 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -1,3 +1,8 @@ +parameters: + - name: OfficialBuild + type: boolean + default: false + jobs: - job: CreateMSIXBundle displayName: Create .msixbundle file @@ -49,7 +54,7 @@ jobs: **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows x86 packages - + # Finds the makeappx tool on the machine with image: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - pwsh: | $cmd = Get-Command makeappx.exe -ErrorAction Ignore @@ -99,12 +104,13 @@ jobs: - task: onebranch.pipeline.signing@1 displayName: Sign MsixBundle + condition: eq('${{ parameters.OfficialBuild }}', 'true') inputs: command: 'sign' signing_profile: $(MSIXProfile) files_to_sign: '**/*.msixbundle' search_root: '$(BundleDir)' - + - pwsh: | $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File Write-Verbose -Verbose "Signed bundle: $signedBundle" @@ -126,12 +132,12 @@ jobs: Get-ChildItem -Path $(System.DefaultWorkingDirectory) -Recurse | Select-Object -ExpandProperty FullName Test-Path -Path '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP-Private.xml' | Write-Verbose -Verbose displayName: Output Pipeline.Workspace and System.DefaultWorkingDirectory - + - template: channelSelection.yml@self - pwsh: | $IsLTS = '$(ChannelSelection.IsLTS)' -eq 'true' - $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' + $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' $IsPreview = '$(ChannelSelection.IsPreview)' -eq 'true' Write-Verbose -Verbose "Channel Selection - LTS: $IsLTS, Stable: $IsStable, Preview: $IsPreview" @@ -161,11 +167,11 @@ jobs: $currentChannel = if ($IsLTS) { 'LTS' } elseif ($IsStable) { 'Stable' } elseif ($IsPreview) { 'Preview' } - else { + else { Write-Error "No valid channel detected" exit 1 } - + $config = $channelConfigs[$currentChannel] Write-Verbose -Verbose "Selected channel: $currentChannel" Write-Verbose -Verbose "App Store Name: $($config.AppStoreName)" @@ -181,7 +187,7 @@ jobs: # Create namespace manager for XML with default namespace $nsManager = New-Object System.Xml.XmlNamespaceManager($pdpXml.NameTable) $nsManager.AddNamespace("pd", "http://schemas.microsoft.com/appx/2012/ProductDescription") - + $appStoreNameElement = $pdpXml.SelectSingleNode("//pd:AppStoreName", $nsManager) if ($appStoreNameElement) { $appStoreNameElement.SetAttribute("_locID", $config.AppStoreName) @@ -220,12 +226,30 @@ jobs: Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" # These variables are used in the next tasks to determine which ServiceEndpoint to use - Write-Host "##vso[task.setvariable variable=LTS]$($IsLTS.ToString().ToLower())" - Write-Host "##vso[task.setvariable variable=STABLE]$($IsStable.ToString().ToLower())" - Write-Host "##vso[task.setvariable variable=PREVIEW]$($IsPreview.ToString().ToLower())" + $ltsValue = $IsLTS.ToString().ToLower() + $stableValue = $IsStable.ToString().ToLower() + $previewValue = $IsPreview.ToString().ToLower() + + Write-Verbose -Verbose "About to set variables:" + Write-Verbose -Verbose " LTS=$ltsValue" + Write-Verbose -Verbose " STABLE=$stableValue" + Write-Verbose -Verbose " PREVIEW=$previewValue" + + Write-Host "##vso[task.setvariable variable=LTS]$ltsValue" + Write-Host "##vso[task.setvariable variable=STABLE]$stableValue" + Write-Host "##vso[task.setvariable variable=PREVIEW]$previewValue" + + Write-Verbose -Verbose "Variables set successfully" name: UpdateConfigs displayName: Update PDPs and SBConfig.json + - pwsh: | + Write-Verbose -Verbose "Checking variables after UpdateConfigs:" + Write-Verbose -Verbose "LTS=$(LTS)" + Write-Verbose -Verbose "STABLE=$(STABLE)" + Write-Verbose -Verbose "PREVIEW=$(PREVIEW)" + displayName: Debug - Check Variables + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 displayName: 'Create StoreBroker Package (Preview)' condition: eq('$(PREVIEW)', 'true') @@ -260,14 +284,14 @@ jobs: $submissionPackageDir = "$(System.DefaultWorkingDirectory)/SBOutDir" $jsonFile = "$submissionPackageDir/PowerShellStorePackage.json" $zipFile = "$submissionPackageDir/PowerShellStorePackage.zip" - + if ((Test-Path $jsonFile) -and (Test-Path $zipFile)) { Write-Verbose -Verbose "Uploading StoreBroker Package files:" Write-Verbose -Verbose "JSON File: $jsonFile" Write-Verbose -Verbose "ZIP File: $zipFile" Copy-Item -Path $submissionPackageDir -Destination "$(ob_outputDirectory)" -Verbose -Recurse - } + } else { Write-Error "Required files not found in $submissionPackageDir" diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml index 9a8be4b18fe..83cdf730b53 100644 --- a/.pipelines/templates/packaging/windows/package.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -60,6 +60,8 @@ jobs: nativePathRoot: '$(Agent.TempDirectory)' ob_restore_phase: false + - template: rebuild-branch-check.yml@self + - download: CoOrdinatedBuildPipeline artifact: drop_windows_build_windows_${{ parameters.runtime }}_release displayName: Download signed artifacts @@ -127,7 +129,21 @@ jobs: Get-PSOptions | Write-Verbose -Verbose $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" if ($LTS) { Write-Verbose -Message "LTS Release: $LTS" diff --git a/.pipelines/templates/rebuild-branch-check.yml b/.pipelines/templates/rebuild-branch-check.yml new file mode 100644 index 00000000000..a4b546a0dc6 --- /dev/null +++ b/.pipelines/templates/rebuild-branch-check.yml @@ -0,0 +1,17 @@ +# This template checks if the current branch is a rebuild branch +# and sets an output variable IsRebuildBranch that can be used by other templates +steps: +- pwsh: | + # Check if this is a rebuild branch (e.g., rebuild/v7.4.13-rebuild.5) + $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' + + $value = if ($isRebuildBranch) { 'true' } else { 'false' } + Write-Verbose -Message "IsRebuildBranch: $value" -Verbose + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected: $(Build.SourceBranch)" -Verbose + } + + Write-Host "##vso[task.setvariable variable=IsRebuildBranch;isOutput=true]$value" + name: RebuildBranchCheck + displayName: Check if Rebuild Branch From 21e5cf3f3f40ae373c4f3b91772d0669e32dde30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:22:18 -0800 Subject: [PATCH 104/255] Bump actions/dependency-review-action from 4.8.1 to 4.8.2 (#26421) --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c3cc0624c45..dadc6fd4370 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 From e069c1d198e01f8982e6263b1128b40538cceb37 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 11 Nov 2025 14:25:51 -0800 Subject: [PATCH 105/255] Update the WCF packages to the latest version that is compatible with v4.10.3 (#26406) --- .../Microsoft.PowerShell.SDK.csproj | 17 +- .../System.Management.Automation.csproj | 7 - tools/packaging/boms/windows.json | 350 +++++++++++++----- tools/packaging/packaging.psm1 | 32 +- 4 files changed, 299 insertions(+), 107 deletions(-) diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 31c48def673..57994fb98f7 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -23,19 +23,10 @@ - - - - - - - - - + + + + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index f3d73f72dd4..114a4c09aaf 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -34,17 +34,10 @@ - - - - - - - diff --git a/tools/packaging/boms/windows.json b/tools/packaging/boms/windows.json index d8857c71786..2999db1a180 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -89,11 +89,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "cs/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "cs/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -159,6 +154,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "cs\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "D3DCompiler_47_cor3.dll", "FileType": "NonProduct", @@ -201,11 +216,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "de/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "de/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -271,6 +281,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "de\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "DirectWriteForwarder.dll", "FileType": "NonProduct", @@ -316,11 +346,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "es/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "es/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -386,6 +411,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "es\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "fr/Microsoft.CodeAnalysis.CSharp.resources.dll", "FileType": "NonProduct", @@ -421,11 +466,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "fr/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "fr/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -491,6 +531,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "fr\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "getfilesiginforedist.dll", "FileType": "NonProduct", @@ -551,11 +611,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "it/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "it/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -621,6 +676,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "it\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "ja/Microsoft.CodeAnalysis.CSharp.resources.dll", "FileType": "NonProduct", @@ -656,11 +731,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "ja/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "ja/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -726,6 +796,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "ja\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "Json.More.dll", "FileType": "NonProduct", @@ -776,11 +866,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "ko/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "ko/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -846,6 +931,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "ko\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "Markdig.Signed.dll", "FileType": "NonProduct", @@ -1111,11 +1216,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "Modules/ThreadJob/*.psd1", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\Microsoft.PowerShell.PSResourceGet.pdb", "FileType": "NonProduct", @@ -1156,16 +1256,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "Modules\\ThreadJob\\.signature.p7s", - "FileType": "NonProduct", - "Architecture": null - }, - { - "Pattern": "Modules\\ThreadJob\\ThreadJob.psm1", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "mscordaccore_*.dll", "FileType": "NonProduct", @@ -1246,11 +1336,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "pl/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "pl/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -1316,6 +1401,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "pl\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "powershell.config.json", "FileType": "NonProduct", @@ -1466,11 +1571,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "pt-BR/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "pt-BR/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -1536,6 +1636,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "pt-BR\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "pwrshplugin.dll", "FileType": "NonProduct", @@ -2431,11 +2551,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "ru/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "ru/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -2501,6 +2616,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "ru\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "Schemas/PSMaml/base.xsd", "FileType": "NonProduct", @@ -3316,11 +3451,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "System.Private.ServiceModel.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "System.Private.Uri.dll", "FileType": "NonProduct", @@ -3606,6 +3736,11 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "System.ServiceModel.NetFramingBase.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "System.ServiceModel.NetTcp.dll", "FileType": "NonProduct", @@ -3901,11 +4036,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "tr/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "tr/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -3971,6 +4101,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "tr\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "UIAutomationClient.dll", "FileType": "NonProduct", @@ -4046,11 +4196,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "zh-Hans/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "zh-Hans/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -4116,6 +4261,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "zh-Hans\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", "FileType": "NonProduct", @@ -4151,11 +4316,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "zh-Hant/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "zh-Hant/System.Web.Services.Description.resources.dll", "FileType": "NonProduct", @@ -4221,6 +4381,26 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "zh-Hant\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "_manifest/spdx_2.2/manifest.spdx.json", "FileType": "Product", diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index e229aa493ae..6966ececaa4 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -284,6 +284,18 @@ function Start-PSPackage { $createdSpdxPathSha = New-Item -Path $manifestSpdxPathSha -Force Write-Verbose -Verbose "Created manifest.spdx.json.sha256 file: $createdSpdxPathSha" } + + $bsiJsonPath = (Join-Path -Path $Source "_manifest\spdx_2.2\bsi.json") + if (-not (Test-Path -Path $bsiJsonPath)) { + $createdBsiJsonPath = New-Item -Path $bsiJsonPath -Force + Write-Verbose -Verbose "Created bsi.json file: $createdBsiJsonPath" + } + + $manifestCatPath = (Join-Path -Path $Source "_manifest\spdx_2.2\manifest.cat") + if (-not (Test-Path -Path $manifestCatPath)) { + $createdCatPath = New-Item -Path $manifestCatPath -Force + Write-Verbose -Verbose "Created manifest.cat file: $createdCatPath" + } } # If building a symbols package, we add a zip of the parent to publish @@ -5521,8 +5533,24 @@ function Send-AzdoFile { Copy-Item -Path $Path -Destination $logFile } - Write-Host "##vso[artifact.upload containerfolder=$newName;artifactname=$newName]$logFile" - Write-Verbose "Log file captured as $newName" -Verbose + Write-Verbose "Capture the log file as '$newName'" -Verbose + if($env:TF_BUILD) { + ## In Azure DevOps + Write-Host "##vso[artifact.upload containerfolder=$newName;artifactname=$newName]$logFile" + } elseif ($env:GITHUB_WORKFLOW -and $env:SYSTEM_ARTIFACTSDIRECTORY) { + ## In GitHub Actions + $destinationPath = $env:SYSTEM_ARTIFACTSDIRECTORY + Write-Verbose "Upload '$logFile' to '$destinationPath' in GitHub Action" -Verbose + + # Create the folder if it does not exist + if (!(Test-Path -Path $destinationPath)) { + $null = New-Item -ItemType Directory -Path $destinationPath -Force + } + + Copy-Item -Path $logFile -Destination $destinationPath -Force -Verbose + } else { + Write-Warning "This environment is neither Azure Devops nor GitHub Actions. Cannot capture the log file in this environment." + } } # Class used for serializing and deserialing a BOM into Json From 2330c80875b1c5db7ebbd283bc52e6fd7d724c1f Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 11 Nov 2025 14:26:46 -0800 Subject: [PATCH 106/255] Update `PSResourceGet` package version to `1.2.0-preview4` (#26404) --- src/Modules/PSGalleryModules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index 6cec561a07a..d2a49e26d2a 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -13,7 +13,7 @@ - + From d412f040b839c10d04b8b3b015eec0d06f12a904 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 12 Nov 2025 08:59:57 -0800 Subject: [PATCH 107/255] Fix template path for rebuild branch check in package.yml (#26425) --- .pipelines/templates/packaging/windows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml index 83cdf730b53..03673d61c7b 100644 --- a/.pipelines/templates/packaging/windows/package.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -60,7 +60,7 @@ jobs: nativePathRoot: '$(Agent.TempDirectory)' ob_restore_phase: false - - template: rebuild-branch-check.yml@self + - template: /.pipelines/templates/rebuild-branch-check.yml@self - download: CoOrdinatedBuildPipeline artifact: drop_windows_build_windows_${{ parameters.runtime }}_release From 1fd3ea835d51f51f7f0b060b34f575bad16f99e0 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 12 Nov 2025 09:35:41 -0800 Subject: [PATCH 108/255] Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26427) --- ...onebranch-condition-syntax.instructions.md | 223 ++++++++++++++++++ .pipelines/templates/package-create-msix.yml | 4 +- 2 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 .github/instructions/onebranch-condition-syntax.instructions.md diff --git a/.github/instructions/onebranch-condition-syntax.instructions.md b/.github/instructions/onebranch-condition-syntax.instructions.md new file mode 100644 index 00000000000..19bf331d9c3 --- /dev/null +++ b/.github/instructions/onebranch-condition-syntax.instructions.md @@ -0,0 +1,223 @@ +--- +applyTo: ".pipelines/**/*.{yml,yaml}" +--- + +# OneBranch Pipeline Condition Syntax + +## Overview +Azure Pipelines (OneBranch) uses specific syntax for referencing variables and parameters in condition expressions. Using the wrong syntax will cause conditions to fail silently or behave unexpectedly. + +## Variable Reference Patterns + +### In Condition Expressions + +**✅ Correct Pattern:** +```yaml +condition: eq(variables['VariableName'], 'value') +condition: or(eq(variables['VAR1'], 'true'), eq(variables['VAR2'], 'true')) +condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) +``` + +**❌ Incorrect Patterns:** +```yaml +# Don't use $(VAR) string expansion in conditions +condition: eq('$(VariableName)', 'value') + +# Don't use direct variable references +condition: eq($VariableName, 'value') +``` + +### In Script Content (pwsh, bash, etc.) + +**✅ Correct Pattern:** +```yaml +- pwsh: | + $value = '$(VariableName)' + Write-Host "Value: $(VariableName)" +``` + +### In Input Fields + +**✅ Correct Pattern:** +```yaml +inputs: + serviceEndpoint: '$(ServiceEndpoint)' + sbConfigPath: '$(SBConfigPath)' +``` + +## Parameter References + +### Template Parameters (Compile-Time) + +**✅ Correct Pattern:** +```yaml +parameters: + - name: OfficialBuild + type: boolean + default: false + +steps: + - task: SomeTask@1 + condition: eq('${{ parameters.OfficialBuild }}', 'true') +``` + +Note: Parameters use `${{ parameters.Name }}` because they're evaluated at template compile-time. + +### Runtime Variables (Execution-Time) + +**✅ Correct Pattern:** +```yaml +steps: + - pwsh: | + Write-Host "##vso[task.setvariable variable=MyVar]somevalue" + displayName: Set Variable + + - task: SomeTask@1 + condition: eq(variables['MyVar'], 'somevalue') +``` + +## Common Scenarios + +### Scenario 1: Check if Variable Equals Value + +```yaml +- task: DoSomething@1 + condition: eq(variables['PREVIEW'], 'true') +``` + +### Scenario 2: Multiple Variable Conditions (OR) + +```yaml +- task: DoSomething@1 + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) +``` + +### Scenario 3: Multiple Variable Conditions (AND) + +```yaml +- task: DoSomething@1 + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) +``` + +### Scenario 4: Complex Conditions + +```yaml +- task: DoSomething@1 + condition: and( + succeededOrFailed(), + ne(variables['UseAzDevOpsFeed'], ''), + eq(variables['Build.SourceBranch'], 'refs/heads/master') + ) +``` + +### Scenario 5: Built-in Variables + +```yaml +- task: CodeQL3000Init@0 + condition: eq(variables['Build.SourceBranch'], 'refs/heads/master') + +- step: finalize + condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues') +``` + +### Scenario 6: Parameter vs Variable + +```yaml +parameters: + - name: OfficialBuild + type: boolean + +steps: + # Parameter condition (compile-time) + - task: SignFiles@1 + condition: eq('${{ parameters.OfficialBuild }}', 'true') + + # Variable condition (runtime) + - task: PublishArtifact@1 + condition: eq(variables['PUBLISH_ENABLED'], 'true') +``` + +## Why This Matters + +**String Expansion `$(VAR)` in Conditions:** +- When you use `'$(VAR)'` in a condition, Azure Pipelines attempts to expand it as a string +- If the variable is undefined or empty, it becomes an empty string `''` +- The condition `eq('', 'true')` will always be false +- This makes debugging difficult because there's no error message + +**Variables Array Syntax `variables['VAR']`:** +- This is the proper way to reference runtime variables in conditions +- Azure Pipelines correctly evaluates the variable's value +- Undefined variables are handled properly by the condition evaluator +- This is the standard pattern used throughout Azure Pipelines + +## Reference Examples + +Working examples can be found in: +- `.pipelines/templates/linux.yml` - Build.SourceBranch conditions +- `.pipelines/templates/windows-hosted-build.yml` - Architecture conditions +- `.pipelines/templates/compliance/apiscan.yml` - CODEQL_ENABLED conditions +- `.pipelines/templates/insert-nuget-config-azfeed.yml` - Complex AND/OR conditions + +## Quick Reference Table + +| Context | Syntax | Example | +|---------|--------|---------| +| Condition expression | `variables['Name']` | `condition: eq(variables['PREVIEW'], 'true')` | +| Script content | `$(Name)` | `pwsh: Write-Host "$(PREVIEW)"` | +| Task input | `$(Name)` | `inputs: path: '$(Build.SourcesDirectory)'` | +| Template parameter | `${{ parameters.Name }}` | `condition: eq('${{ parameters.Official }}', 'true')` | + +## Troubleshooting + +### Condition Always False +If your condition is always evaluating to false: +1. Check if you're using `'$(VAR)'` instead of `variables['VAR']` +2. Verify the variable is actually set (add a debug step to print the variable) +3. Check the variable value is exactly what you expect (case-sensitive) + +### Variable Not Found +If you get errors about variables not being found: +1. Ensure the variable is set before the condition is evaluated +2. Check that the variable name is spelled correctly +3. Verify the variable is in scope (job vs. stage vs. pipeline level) + +## Best Practices + +1. **Always use `variables['Name']` in conditions** - This is the correct Azure Pipelines pattern +2. **Use `$(Name)` for string expansion** in scripts and inputs +3. **Use `${{ parameters.Name }}` for template parameters** (compile-time) +4. **Add debug steps** to verify variable values when troubleshooting conditions +5. **Follow existing patterns** in the repository - grep for `condition:` to see examples + +## Common Mistakes + +❌ **Mistake 1: String expansion in condition** +```yaml +condition: eq('$(PREVIEW)', 'true') # WRONG +``` + +✅ **Fix:** +```yaml +condition: eq(variables['PREVIEW'], 'true') # CORRECT +``` + +❌ **Mistake 2: Missing quotes around parameter** +```yaml +condition: eq(${{ parameters.Official }}, true) # WRONG +``` + +✅ **Fix:** +```yaml +condition: eq('${{ parameters.Official }}', 'true') # CORRECT +``` + +❌ **Mistake 3: Mixing syntax** +```yaml +condition: or(eq('$(STABLE)', 'true'), eq(variables['LTS'], 'true')) # INCONSISTENT +``` + +✅ **Fix:** +```yaml +condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) # CORRECT +``` diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index 2b32aa4d0bb..255915ab02c 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -252,7 +252,7 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 displayName: 'Create StoreBroker Package (Preview)' - condition: eq('$(PREVIEW)', 'true') + condition: eq(variables['PREVIEW'], 'true') inputs: serviceEndpoint: 'StoreAppPublish-Preview' sbConfigPath: '$(SBConfigPath)' @@ -264,7 +264,7 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 displayName: 'Create StoreBroker Package (Stable/LTS)' - condition: or(eq('$(STABLE)', 'true'), eq('$(LTS)', 'true')) + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) inputs: serviceEndpoint: 'StoreAppPublish-Stable' sbConfigPath: '$(SBConfigPath)' From 4aa65a77be93911072e015f2a52656786c8f54bf Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 12 Nov 2025 09:36:50 -0800 Subject: [PATCH 109/255] Update the macos package name for preview releases to match the previous pattern (#26429) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../templates/release-validate-packagenames.yml | 2 +- test/packaging/macos/package-validation.tests.ps1 | 11 ++++++----- tools/packaging/packaging.psm1 | 9 +++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.pipelines/templates/release-validate-packagenames.yml b/.pipelines/templates/release-validate-packagenames.yml index c717b50f289..6344418cd8f 100644 --- a/.pipelines/templates/release-validate-packagenames.yml +++ b/.pipelines/templates/release-validate-packagenames.yml @@ -82,7 +82,7 @@ jobs: - pwsh: | $message = @() Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -filter *.pkg | ForEach-Object { - if($_.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx(\.10\.12)?\-(x64|arm64)\.pkg') + if($_.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx\-(x64|arm64)\.pkg') { $messageInstance = "$($_.Name) is not a valid package name" $message += $messageInstance diff --git a/test/packaging/macos/package-validation.tests.ps1 b/test/packaging/macos/package-validation.tests.ps1 index 02b09fbb078..945ffea6f7a 100644 --- a/test/packaging/macos/package-validation.tests.ps1 +++ b/test/packaging/macos/package-validation.tests.ps1 @@ -82,12 +82,13 @@ Describe "Verify macOS Package" { $script:package | Should -Not -BeNullOrEmpty # Regex pattern for valid macOS PKG package names. + # This pattern matches the validation used in release-validate-packagenames.yml # Valid examples: - # - powershell-7.4.13-osx-x64.pkg (Intel x64 - note: x64 with hyphens for compatibility) - # - powershell-7.4.13-osx-arm64.pkg (Apple Silicon) - # - powershell-preview-7.6.0-preview.6-osx-x64.pkg - # - powershell-lts-7.4.13-osx-arm64.pkg - $pkgPackageNamePattern = '^powershell(-preview|-lts)?-\d+\.\d+\.\d+(-[a-z]+\.\d+)?-osx-(x64|arm64)\.pkg$' + # - powershell-7.4.13-osx-x64.pkg (Stable release) + # - powershell-7.6.0-preview.6-osx-x64.pkg (Preview version string) + # - powershell-7.4.13-rebuild.5-osx-arm64.pkg (Rebuild version) + # - powershell-lts-7.4.13-osx-arm64.pkg (LTS package) + $pkgPackageNamePattern = '^powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx\-(x64|arm64)\.pkg$' $script:package.Name | Should -Match $pkgPackageNamePattern -Because "Package name should follow the standard naming convention" } diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 6966ececaa4..baeb12e360a 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1159,15 +1159,12 @@ function New-UnixPackage { } # Determine if the version is a preview version - $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS - - # Preview versions have preview in the name + # Only LTS packages get a prefix in the name + # Preview versions are identified by the version string itself (e.g., 7.6.0-preview.6) + # Rebuild versions are also identified by the version string (e.g., 7.4.13-rebuild.5) $Name = if($LTS) { "powershell-lts" } - elseif ($IsPreview) { - "powershell-preview" - } else { "powershell" } From 5715d33398d97bed2ccf9e6526faea3db1626b88 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 13 Nov 2025 09:56:09 -0800 Subject: [PATCH 110/255] DSC v3 resource for Powershell Profile (#26157) --- .github/actions/test/nix/action.yml | 42 ++++ .github/actions/test/windows/action.yml | 20 ++ dsc/pwsh.profile.dsc.resource.json | 126 +++++++++++ dsc/pwsh.profile.resource.ps1 | 179 +++++++++++++++ experimental-feature-linux.json | 1 + experimental-feature-windows.json | 1 + .../ExperimentalFeature.cs | 5 + src/powershell-unix/powershell-unix.csproj | 4 + .../powershell-win-core.csproj | 4 + .../dsc/dsc.profileresource.Tests.ps1 | 204 ++++++++++++++++++ .../dsc/psprofile_alluser_allhost.dsc.yaml | 9 + .../psprofile_allusers_currenthost.dsc.yaml | 9 + .../psprofile_currentuser_allhosts.dsc.yaml | 9 + ...psprofile_currentuser_currenthost.dsc.yaml | 9 + ..._currentuser_currenthost_emptycontent.yaml | 9 + test/powershell/dsc/psprofile_export.dsc.yaml | 5 + tools/packaging/boms/linux.json | 10 + tools/packaging/boms/mac.json | 10 + tools/packaging/boms/windows.json | 10 + 19 files changed, 666 insertions(+) create mode 100644 dsc/pwsh.profile.dsc.resource.json create mode 100644 dsc/pwsh.profile.resource.ps1 create mode 100644 test/powershell/dsc/dsc.profileresource.Tests.ps1 create mode 100644 test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml create mode 100644 test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml create mode 100644 test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml create mode 100644 test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml create mode 100644 test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml create mode 100644 test/powershell/dsc/psprofile_export.dsc.yaml diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml index ef943bfce78..b8c1778974c 100644 --- a/.github/actions/test/nix/action.yml +++ b/.github/actions/test/nix/action.yml @@ -43,6 +43,48 @@ runs: with: global-json-file: ./global.json + - name: Set Package Name by Platform + id: set_package_name + shell: pwsh + run: |- + Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 + $platform = $env:RUNNER_OS + Write-Host "Runner platform: $platform" + if ($platform -eq 'Linux') { + $packageName = 'DSC-*-x86_64-linux.tar.gz' + } elseif ($platform -eq 'macOS') { + $packageName = 'DSC-*-x86_64-apple-darwin.tar.gz' + } else { + throw "Unsupported platform: $platform" + } + + Set-GWVariable -Name "DSC_PACKAGE_NAME" -Value $packageName + + - name: Get Latest DSC Package Version + shell: pwsh + run: |- + Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" + $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 + $latestVersion = $latestRelease.tag_name.TrimStart("v") + Write-Host "Latest DSC Version: $latestVersion" + + $packageName = "$env:DSC_PACKAGE_NAME" + + Write-Host "Package Name: $packageName" + + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "*$packageName*" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url + Write-Host "Download URL: $downloadUrl" + + $tempPath = Get-GWTempPath + + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath/DSC.tar.gz" -Verbose + New-Item -ItemType Directory -Path "$tempPath/DSC" -Force -Verbose + tar xvf "$tempPath/DSC.tar.gz" -C "$tempPath/DSC" + $dscRoot = "$tempPath/DSC" + Write-Host "DSC Root: $dscRoot" + Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot + - name: Bootstrap shell: pwsh run: |- diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml index 3b3ce0cafe8..923e9606e59 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -43,6 +43,26 @@ runs: with: global-json-file: .\global.json + - name: Get Latest DSC Package Version + shell: pwsh + run: |- + Import-Module .\.github\workflows\GHWorkflowHelper\GHWorkflowHelper.psm1 + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" + $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 + $latestVersion = $latestRelease.tag_name.TrimStart("v") + Write-Host "Latest DSC Version: $latestVersion" + + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url + Write-Host "Download URL: $downloadUrl" + $tempPath = Get-GWTempPath + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath\DSC.zip" + + $null = New-Item -ItemType Directory -Path "$tempPath\DSC" -Force + Expand-Archive -Path "$tempPath\DSC.zip" -DestinationPath "$tempPath\DSC" -Force + $dscRoot = "$tempPath\DSC" + Write-Host "DSC Root: $dscRoot" + Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot + - name: Bootstrap shell: powershell run: |- diff --git a/dsc/pwsh.profile.dsc.resource.json b/dsc/pwsh.profile.dsc.resource.json new file mode 100644 index 00000000000..cd18e94eec6 --- /dev/null +++ b/dsc/pwsh.profile.dsc.resource.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "description": "Manage PowerShell profiles.", + "tags": [ + "Linux", + "Windows", + "macOS", + "PowerShell" + ], + "type": "Microsoft.PowerShell/Profile", + "version": "0.1.0", + "get": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "get" + ], + "input": "stdin" + }, + "set": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "set" + ], + "input": "stdin" + }, + "export": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "export" + ], + "input": "stdin" + }, + "exitCodes": { + "0": "Success", + "1": "Error", + "2": "Input not supported for export operation" + }, + "schema": { + "embedded": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Profile", + "description": "Manage PowerShell profiles.", + "type": "object", + "unevaluatedProperties": false, + "required": [ + "profileType" + ], + "properties": { + "profileType": { + "type": "string", + "title": "Profile Type", + "description": "Defines which profile to manage. Valid values are: 'AllUsersCurrentHost', 'AllUsersAllHosts', 'CurrentUserAllHosts', and 'CurrentUserCurrentHost'.", + "enum": [ + "AllUsersCurrentHost", + "AllUsersAllHosts", + "CurrentUserAllHosts", + "CurrentUserCurrentHost" + ] + }, + "profilePath": { + "title": "Profile Path", + "description": "The full path to the profile file.", + "type": "string", + "readOnly": true + }, + "content": { + "title": "Content", + "description": "Defines the content of the profile. If you don't specify this property, the resource doesn't manage the file contents. If you specify this property as an empty string, the resource removes all content from the file. If you specify this property as a non-empty string, the resource sets the file contents to the specified string. The resources retains newlines from this property without any modification.", + "type": "string" + }, + "_exist": { + "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json" + }, + "_name": { + "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json" + } + }, + "$defs": { + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json", + "title": "Instance should exist", + "description": "Indicates whether the DSC resource instance should exist.", + "type": "boolean", + "default": true, + "enum": [ + false, + true + ] + }, + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json", + "title": "Exported instance name", + "description": "Returns a generated name for the resource instance from an export operation.", + "readOnly": true, + "type": "string" + } + } + } + } +} diff --git a/dsc/pwsh.profile.resource.ps1 b/dsc/pwsh.profile.resource.ps1 new file mode 100644 index 00000000000..ad9cfa4a63a --- /dev/null +++ b/dsc/pwsh.profile.resource.ps1 @@ -0,0 +1,179 @@ +## Copyright (c) Microsoft Corporation. All rights reserved. +## Licensed under the MIT License. + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateSet('get', 'set', 'export')] + [string]$Operation, + [Parameter(ValueFromPipeline)] + [string[]]$UserInput +) + +Begin { + enum ProfileType { + AllUsersCurrentHost + AllUsersAllHosts + CurrentUserAllHosts + CurrentUserCurrentHost + } + + function New-PwshResource { + param( + [Parameter(Mandatory = $true)] + [ProfileType] $ProfileType, + + [Parameter(ParameterSetName = 'WithContent')] + [string] $Content, + + [Parameter(ParameterSetName = 'WithContent')] + [bool] $Exist + ) + + # Create the PSCustomObject with properties + $resource = [PSCustomObject]@{ + profileType = $ProfileType + content = $null + profilePath = GetProfilePath -profileType $ProfileType + _exist = $false + } + + # Add ToJson method + $resource | Add-Member -MemberType ScriptMethod -Name 'ToJson' -Value { + return ([ordered] @{ + profileType = $this.profileType + content = $this.content + profilePath = $this.profilePath + _exist = $this._exist + }) | ConvertTo-Json -Compress -EnumsAsStrings + } + + # Constructor logic - if Content and Exist parameters are provided (WithContent parameter set) + if ($PSCmdlet.ParameterSetName -eq 'WithContent') { + $resource.content = $Content + $resource._exist = $Exist + } else { + # Default constructor logic - read from file system + $fileExists = Test-Path $resource.profilePath + if ($fileExists) { + $resource.content = Get-Content -Path $resource.profilePath + } else { + $resource.content = $null + } + $resource._exist = $fileExists + } + + return $resource + } + + function GetProfilePath { + param ( + [ProfileType] $profileType + ) + + $path = switch ($profileType) { + 'AllUsersCurrentHost' { $PROFILE.AllUsersCurrentHost } + 'AllUsersAllHosts' { $PROFILE.AllUsersAllHosts } + 'CurrentUserAllHosts' { $PROFILE.CurrentUserAllHosts } + 'CurrentUserCurrentHost' { $PROFILE.CurrentUserCurrentHost } + } + + return $path + } + + function ExportOperation { + $allUserCurrentHost = New-PwshResource -ProfileType 'AllUsersCurrentHost' + $allUsersAllHost = New-PwshResource -ProfileType 'AllUsersAllHosts' + $currentUserAllHost = New-PwshResource -ProfileType 'CurrentUserAllHosts' + $currentUserCurrentHost = New-PwshResource -ProfileType 'CurrentUserCurrentHost' + + # Cannot use the ToJson() method here as we are adding a note property + $allUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $allUsersAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $currentUserAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $currentUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + } + + function GetOperation { + param ( + [Parameter(Mandatory = $true)] + $InputResource, + [Parameter()] + [switch] $AsJson + ) + + $profilePath = GetProfilePath -profileType $InputResource.profileType.ToString() + + $actualState = New-PwshResource -ProfileType $InputResource.profileType + + $actualState.profilePath = $profilePath + + $exists = Test-Path $profilePath + + if ($InputResource._exist -and $exists) { + $content = Get-Content -Path $profilePath + $actualState.Content = $content + } elseif ($InputResource._exist -and -not $exists) { + $actualState.Content = $null + $actualState._exist = $false + } elseif (-not $InputResource._exist -and $exists) { + $actualState.Content = Get-Content -Path $profilePath + $actualState._exist = $true + } else { + $actualState.Content = $null + $actualState._exist = $false + } + + if ($AsJson) { + return $actualState.ToJson() + } else { + return $actualState + } + } + + function SetOperation { + param ( + $InputResource + ) + + $actualState = GetOperation -InputResource $InputResource + + if ($InputResource._exist) { + if (-not $actualState._exist) { + $null = New-Item -Path $actualState.profilePath -ItemType File -Force + } + + if ($null -ne $InputResource.content) { + Set-Content -Path $actualState.profilePath -Value $InputResource.content + } + } elseif ($actualState._exist) { + Remove-Item -Path $actualState.profilePath -Force + } + } +} +End { + $inputJson = $input | ConvertFrom-Json + + if ($inputJson) { + $InputResource = New-PwshResource -ProfileType $inputJson.profileType -Content $inputJson.content -Exist $inputJson._exist + } + + switch ($Operation) { + 'get' { + GetOperation -InputResource $InputResource -AsJson + } + 'set' { + SetOperation -InputResource $InputResource + } + 'export' { + if ($inputJson) { + Write-Error "Input not supported for export operation" + exit 2 + } + + ExportOperation + } + } + + exit 0 +} diff --git a/experimental-feature-linux.json b/experimental-feature-linux.json index ca5b49878a4..31f7b965a5b 100644 --- a/experimental-feature-linux.json +++ b/experimental-feature-linux.json @@ -2,6 +2,7 @@ "PSFeedbackProvider", "PSLoadAssemblyFromNativeCode", "PSNativeWindowsTildeExpansion", + "PSProfileDSCResource", "PSSerializeJSONLongEnumAsNumber", "PSRedirectToVariable", "PSSubsystemPluginModel" diff --git a/experimental-feature-windows.json b/experimental-feature-windows.json index ca5b49878a4..31f7b965a5b 100644 --- a/experimental-feature-windows.json +++ b/experimental-feature-windows.json @@ -2,6 +2,7 @@ "PSFeedbackProvider", "PSLoadAssemblyFromNativeCode", "PSNativeWindowsTildeExpansion", + "PSProfileDSCResource", "PSSerializeJSONLongEnumAsNumber", "PSRedirectToVariable", "PSSubsystemPluginModel" diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 1c17a22ae9a..7e17ec43137 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -21,6 +21,7 @@ public class ExperimentalFeature internal const string EngineSource = "PSEngine"; internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); + internal const string PSProfileDSCResource = "PSProfileDSCResource"; #endregion @@ -109,6 +110,10 @@ static ExperimentalFeature() new ExperimentalFeature( name: PSSerializeJSONLongEnumAsNumber, description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." + ), + new ExperimentalFeature( + name: PSProfileDSCResource, + description: "DSC v3 resources for managing PowerShell profile." ) }; diff --git a/src/powershell-unix/powershell-unix.csproj b/src/powershell-unix/powershell-unix.csproj index 802acf05e3a..20c61247d24 100644 --- a/src/powershell-unix/powershell-unix.csproj +++ b/src/powershell-unix/powershell-unix.csproj @@ -37,6 +37,10 @@ PreserveNewest PreserveNewest + + PreserveNewest + PreserveNewest + diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj index 5368518dd3c..e6efeac10f0 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -29,6 +29,10 @@ PreserveNewest PreserveNewest + + PreserveNewest + PreserveNewest + PreserveNewest PreserveNewest diff --git a/test/powershell/dsc/dsc.profileresource.Tests.ps1 b/test/powershell/dsc/dsc.profileresource.Tests.ps1 new file mode 100644 index 00000000000..f361e67ea8e --- /dev/null +++ b/test/powershell/dsc/dsc.profileresource.Tests.ps1 @@ -0,0 +1,204 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "DSC PowerShell Profile Resource Tests" -Tag "CI" { + BeforeAll { + $DSC_ROOT = $env:DSC_ROOT + + if (-not (Test-Path -Path $DSC_ROOT)) { + throw "DSC_ROOT environment variable is not set or path does not exist." + } + + Write-Verbose "DSC_ROOT is set to $DSC_ROOT" -Verbose + + $originalPath = $env:PATH + + $pathSeparator = [System.IO.Path]::PathSeparator + $env:PATH += "$pathSeparator$DSC_ROOT" + $env:PATH += "$pathSeparator$PSHome" + + Write-Verbose "Updated PATH to include DSC_ROOT: $env:PATH" -Verbose + + # Ensure DSC v3 is available + if (-not (Get-Command -name dsc -CommandType Application -ErrorAction SilentlyContinue)) { + Get-ChildItem $DSC_ROOT -Recurse 'dsc' | ForEach-Object { + Write-Verbose "Found DSC executable at $($_.FullName)" -Verbose + } + throw "DSC v3 is not installed" + } + + $dscExe = Get-Command -name dsc -CommandType Application | Select-Object -First 1 + + $testProfileContent = "# Test profile content currentuser currenthost" + $testProfilePathCurrentUserCurrentHost = $PROFILE.CurrentUserCurrentHost + Copy-Item -Path $testProfilePathCurrentUserCurrentHost -Destination "$TestDrive/currentuser-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathCurrentUserCurrentHost -Value $testProfileContent -Force -ItemType File + + $testProfileContent = "# Test profile content currentuser allhosts" + $testProfilePathCurrentUserAllHosts = $PROFILE.CurrentUserAllHosts + Copy-Item -Path $testProfilePathCurrentUserAllHosts -Destination "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathCurrentUserAllHosts -Value $testProfileContent -Force -ItemType File + } + AfterAll { + # Restore original profile + $testProfilePathCurrentUserCurrentHost = $PROFILE.CurrentUserCurrentHost + if (Test-Path "$TestDrive/currentuser-currenthost-profile.bak") { + Copy-Item -Path "$TestDrive/currentuser-currenthost-profile.bak" -Destination $testProfilePathCurrentUserCurrentHost -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathCurrentUserCurrentHost -Force -ErrorAction SilentlyContinue + } + + $testProfilePathCurrentUserAllHosts = $PROFILE.CurrentUserAllHosts + if (Test-Path "$TestDrive/currentuser-allhosts-profile.bak") { + Copy-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Destination $testProfilePathCurrentUserAllHosts -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathCurrentUserAllHosts -Force -ErrorAction SilentlyContinue + } + + $env:PATH = $originalPath + Remove-Item -Path "$TestDrive/currentuser-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + } + + It 'DSC resource is located at $PSHome' { + $resourceFile = Join-Path -Path $PSHome -ChildPath 'pwsh.profile.resource.ps1' + $resourceFile | Should -Exist + + $resourceManifest = Join-Path -Path $PSHome -ChildPath 'pwsh.profile.dsc.resource.json' + $resourceManifest | Should -Exist + } + + It 'DSC resource can be found' { + (& $dscExe resource list -o json | ConvertFrom-Json | Select-Object -Property type).type | Should -Contain 'Microsoft.PowerShell/Profile' + } + + It 'DSC resource can set current user current host profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_currentuser_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserCurrentHost!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get current user current host profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_currentuser_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserCurrentHost!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can set content as empty for current user current host profile' -Pending { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_currentuser_currenthost_emptycontent.dsc.yaml -o json) | ConvertFrom-Json + $setOutput.results.result.afterState.content | Should -BeExactly '' + } + + It 'DSC resource can set current user all hosts profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_currentuser_allhosts.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserAllHosts!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get current user all hosts profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_currentuser_allhosts.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserAllHosts!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can export all profiles' { + $exportOutput = (& $dscExe config export --file $PSScriptRoot/psprofile_export.dsc.yaml -o json) | ConvertFrom-Json + + $exportOutput.resources | Should -HaveCount 4 + + $exportOutput.resources | ForEach-Object { + $_.type | Should -Be 'Microsoft.PowerShell/Profile' + $_.name | Should -BeIn @('AllUsersCurrentHost', 'AllUsersAllHosts', 'CurrentUserCurrentHost', 'CurrentUserAllHosts') + } + } +} + +Describe "DSC PowerShell Profile resource elevated tests" -Tag "CI", 'RequireAdminOnWindows', 'RequireSudoOnUnix' { + BeforeAll { + $DSC_ROOT = $env:DSC_ROOT + + if (-not (Test-Path -Path $DSC_ROOT)) { + throw "DSC_ROOT environment variable is not set or path does not exist." + } + + Write-Verbose "DSC_ROOT is set to $DSC_ROOT" -Verbose + $pathSeparator = [System.IO.Path]::PathSeparator + + $env:PATH += "$pathSeparator$DSC_ROOT" + + $env:PATH += "$pathSeparator$PSHome" + + Write-Verbose "Updated PATH to include DSC_ROOT: $env:PATH" -Verbose + + # Ensure DSC v3 is available + if (-not (Get-Command -name dsc -CommandType Application -ErrorAction SilentlyContinue)) { + Get-ChildItem $DSC_ROOT -Recurse 'dsc' | ForEach-Object { + Write-Verbose "Found DSC executable at $($_.FullName)" -Verbose + } + throw "DSC v3 is not installed" + } + + $dscExe = Get-Command -name dsc -CommandType Application | Select-Object -First 1 + + $testProfileContent = "# Test profile content allusers currenthost" + $testProfilePathAllUsersCurrentHost = $PROFILE.AllUsersCurrentHost + Copy-Item -Path $testProfilePathAllUsersCurrentHost -Destination "$TestDrive/allusers-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathAllUsersCurrentHost -Value $testProfileContent -Force -ItemType File + + $testProfileContent = "# Test profile content allusers allhosts" + $testProfilePathAllUsersAllHosts = $PROFILE.AllUsersAllHosts + Copy-Item -Path $testProfilePathAllUsersAllHosts -Destination "$TestDrive/allusers-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathAllUsersAllHosts -Value $testProfileContent -Force -ItemType File + + $originalPath = $env:PATH + $env:PATH += "$pathSeparator$PSHome" + } + AfterAll { + $env:PATH = $originalPath + + $testProfilePathAllUsersCurrentHost = $PROFILE.AllUsersCurrentHost + if (Test-Path "$TestDrive/allusers-currenthost-profile.bak") { + Copy-Item -Path "$TestDrive/allusers-currenthost-profile.bak" -Destination $testProfilePathAllUsersCurrentHost -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathAllUsersCurrentHost -Force -ErrorAction SilentlyContinue + } + + $testProfilePathAllUsersAllHosts = $PROFILE.AllUsersAllHosts + if (Test-Path "$TestDrive/allusers-allhosts-profile.bak") { + Copy-Item -Path "$TestDrive/allusers-allhosts-profile.bak" -Destination $testProfilePathAllUsersAllHosts -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathAllUsersAllHosts -Force -ErrorAction SilentlyContinue + } + + Remove-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$TestDrive/allusers-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + } + + It 'DSC resource can set all users all hosts profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_alluser_allhost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersAllHosts!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get all users all hosts profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_alluser_allhost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersAllHosts!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can set all users current hosts profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_allusers_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersCurrentHost!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get all users current hosts profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_allusers_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersCurrentHost!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } +} diff --git a/test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml b/test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml new file mode 100644 index 00000000000..356119826c3 --- /dev/null +++ b/test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: AllUsersAllHosts + content: "Write-Host 'Welcome to your PowerShell profile - AllUsersAllHosts!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml b/test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml new file mode 100644 index 00000000000..bc51f0a4392 --- /dev/null +++ b/test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: AllUsersCurrentHost + content: "Write-Host 'Welcome to your PowerShell profile - AllUsersCurrentHost!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml b/test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml new file mode 100644 index 00000000000..8ed8d98c3ab --- /dev/null +++ b/test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: CurrentUserAllHosts + content: "Write-Host 'Welcome to your PowerShell profile - CurrentUserAllHosts!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml b/test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml new file mode 100644 index 00000000000..5a42c28eb96 --- /dev/null +++ b/test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: CurrentUserCurrentHost + content: "Write-Host 'Welcome to your PowerShell profile - CurrentUserCurrentHost!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml b/test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml new file mode 100644 index 00000000000..76439387d2e --- /dev/null +++ b/test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: CurrentUserCurrentHost + content: '' + _exist: true diff --git a/test/powershell/dsc/psprofile_export.dsc.yaml b/test/powershell/dsc/psprofile_export.dsc.yaml new file mode 100644 index 00000000000..cf7881b5df8 --- /dev/null +++ b/test/powershell/dsc/psprofile_export.dsc.yaml @@ -0,0 +1,5 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile diff --git a/tools/packaging/boms/linux.json b/tools/packaging/boms/linux.json index 9f5f886a4ca..db29e47a290 100644 --- a/tools/packaging/boms/linux.json +++ b/tools/packaging/boms/linux.json @@ -2442,5 +2442,15 @@ { "Pattern": "System.Management.Automation.dll", "FileType": "Product" + }, + { + "Pattern": "pwsh.profile.dsc.resource.json", + "FileType": "Product", + "Architecture": null + }, + { + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null } ] diff --git a/tools/packaging/boms/mac.json b/tools/packaging/boms/mac.json index a4b5bc2bffb..af4fcb0c967 100644 --- a/tools/packaging/boms/mac.json +++ b/tools/packaging/boms/mac.json @@ -2218,5 +2218,15 @@ { "Pattern": "System.Management.Automation.dll", "FileType": "Product" + }, + { + "Pattern": "pwsh.profile.dsc.resource.json", + "FileType": "Product", + "Architecture": null + }, + { + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null } ] diff --git a/tools/packaging/boms/windows.json b/tools/packaging/boms/windows.json index 2999db1a180..f811109f818 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -4605,5 +4605,15 @@ "Pattern": "System.Management.Automation.dll", "FileType": "Product", "Architecture": null + }, + { + "Pattern": "pwsh.profile.dsc.resource.json", + "FileType": "Product", + "Architecture": null + }, + { + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null } ] From 250e18cc9bbbf06a5562f3071355ff24ce92f880 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:06:49 +0000 Subject: [PATCH 111/255] Refactor IsComputerNameValid character validation (#26274) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../commands/management/Computer.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs index 0ea1c91aae6..ea8c111531c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs @@ -2019,35 +2019,24 @@ internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, st /// internal static bool IsComputerNameValid(string computerName) { - bool allDigits = true; + bool hasAsciiLetterOrHyphen = false; if (computerName.Length >= 64) return false; foreach (char t in computerName) { - if (t >= 'A' && t <= 'Z' || - t >= 'a' && t <= 'z') + if (char.IsAsciiLetter(t) || t is '-') { - allDigits = false; - continue; - } - else if (t >= '0' && t <= '9') - { - continue; + hasAsciiLetterOrHyphen = true; } - else if (t == '-') - { - allDigits = false; - continue; - } - else + else if (!char.IsAsciiDigit(t)) { return false; } } - return !allDigits; + return hasAsciiLetterOrHyphen; } /// From 77f1d99568dd809d6cbc10f02ea6a586070a5ea9 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Mon, 17 Nov 2025 12:36:48 +0900 Subject: [PATCH 112/255] Respect -Shuffle:$false in Get-Random (#26457) --- .../commands/utility/GetRandomCommandBase.cs | 2 +- .../Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs index fef7764c5b8..8e50b1cf5a9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs @@ -488,7 +488,7 @@ protected override void ProcessRecord() { if (EffectiveParameterSet == MyParameterSet.RandomListItem) { - if (ParameterSetName == ShuffleParameterSet) + if (Shuffle) { // this allows for $null to be in an array passed to InputObject foreach (object item in InputObject ?? _nullInArray) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 index cd9fee41873..100ca90b357 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 @@ -162,6 +162,12 @@ Describe "Get-Random" -Tags "CI" { $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 } + It "Should return a single random item when -Shuffle:`$false is used" { + $randomNumber = Get-Random -InputObject 1, 2, 3, 5, 8, 13 -Shuffle:$false + $randomNumber.Count | Should -Be 1 + $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 + } + It "Should return for a string collection " { $randomNumber = Get-Random -InputObject "red", "yellow", "blue" $randomNumber | Should -Be ("red" -or "yellow" -or "blue") From e741cecab394958337a21fd54e280950197df44b Mon Sep 17 00:00:00 2001 From: yotsuda Date: Mon, 17 Nov 2025 15:45:14 +0900 Subject: [PATCH 113/255] Respect -Shuffle:$false in Get-SecureRandom (#26460) --- .../Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 index acc560abdb3..957bfa30e9e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 @@ -150,6 +150,12 @@ Describe "Get-SecureRandom" -Tags "CI" { $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 } + It "Should return a single random item when -Shuffle:`$false is used" { + $randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13 -Shuffle:$false + $randomNumber.Count | Should -Be 1 + $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 + } + It "Should return for a string collection " { $randomNumber = Get-SecureRandom -InputObject "red", "yellow", "blue" $randomNumber | Should -Be ("red" -or "yellow" -or "blue") From 11d154c6730dc2ef05c4df09de119b2dc5a4fa88 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Tue, 18 Nov 2025 02:04:57 +0900 Subject: [PATCH 114/255] Respect -ListAvailable:$false in Get-TimeZone (#26463) --- .../commands/management/TimeZoneCommands.cs | 2 +- .../Microsoft.PowerShell.Management/TimeZone.Tests.ps1 | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs index 8fdb2c55acd..22a50e41176 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs @@ -54,7 +54,7 @@ protected override void ProcessRecord() // make sure we've got the latest time zone settings TimeZoneInfo.ClearCachedData(); - if (this.ParameterSetName.Equals("ListAvailable", StringComparison.OrdinalIgnoreCase)) + if (ListAvailable) { // output the list of all available time zones WriteObject(TimeZoneInfo.GetSystemTimeZones(), true); diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 index 5a23c7df75a..a3904fca41f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 @@ -71,6 +71,12 @@ Describe "Get-Timezone test cases" -Tags "CI" { $oneExpectedOffset | Should -BeIn $observedIdList } + It "Call with -ListAvailable:`$false returns current TimeZoneInfo (not list)" { + $result = Get-TimeZone -ListAvailable:$false + $result | Should -BeOfType TimeZoneInfo + $result.Id | Should -Be ([System.TimeZoneInfo]::Local).Id + } + It "Call Get-TimeZone using ID param and single item" { $selectedTZ = $TimeZonesAvailable[0] (Get-TimeZone -Id $selectedTZ.Id).Id | Should -Be $selectedTZ.Id From 6d61ef1034f4b6866fa1e727eb1d157427df28d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:51:56 -0800 Subject: [PATCH 115/255] Bump github/codeql-action from 4.31.2 to 4.31.3 (#26451) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index a16a840a606..6b9878be9ea 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 4246af344fd..4c502ed38a3 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 with: sarif_file: results.sarif From bb44aa1bc4e83dcf4ab7cf31bf9ae241b395f86b Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:56:51 +0200 Subject: [PATCH 116/255] Mirror .NET/runtime ICU version range in PowerShell Linux packaging (#26304) --- tools/packaging/packaging.psm1 | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index baeb12e360a..8f9cec71790 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -2125,6 +2125,22 @@ function Get-PackageDependencies # These should match those in the Dockerfiles, but exclude tools like Git, which, and curl $Dependencies = @() + + # ICU version range follows .NET runtime policy. + # See: https://github.com/dotnet/runtime/blob/3fe8518d51bbcaa179bbe275b2597fbe1b88bc5a/src/native/libs/System.Globalization.Native/pal_icushim.c#L235-L243 + # + # Version range rationale: + # - The runtime supports ICU versions >= the version it was built against + # and <= that version + 30, to allow sufficient headroom for future releases. + # - ICU typically releases about twice per year, so +30 provides roughly + # 15 years of forward compatibility. + # - On some platforms, the minimum supported version may be lower + # than the build version and we know that older versions just works. + # + $MinICUVersion = 60 # runtime minimum supported + $BuildICUVersion = 76 # current build version + $MaxICUVersion = $BuildICUVersion + 30 # headroom + if ($Distribution -eq 'deb') { $Dependencies = @( "libc6", @@ -2132,10 +2148,9 @@ function Get-PackageDependencies "libgssapi-krb5-2", "libstdc++6", "zlib1g", - "libicu76|libicu74|libicu72|libicu71|libicu70|libicu69|libicu68|libicu67|libicu66|libicu65|libicu63|libicu60|libicu57|libicu55|libicu52", + (($MaxICUVersion..$MinICUVersion).ForEach{ "libicu$_" } -join '|'), "libssl3|libssl1.1|libssl1.0.2|libssl1.0.0" ) - } elseif ($Distribution -eq 'rh') { $Dependencies = @( "openssl-libs", From 015b5cd80152c3f4e44b40293a18e7798a18b1aa Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:26:45 -0800 Subject: [PATCH 117/255] Update build to use .NET SDK 10.0.100 (#26448) --- DotnetRuntimeMetadata.json | 2 +- global.json | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 10 +- .../Microsoft.WSMan.Management.csproj | 2 +- .../System.Management.Automation.csproj | 12 +- .../BenchmarkDotNet.Extensions.csproj | 4 +- .../ResultsComparer/ResultsComparer.csproj | 4 +- .../nested/Test.Isolated.Nested.csproj | 2 +- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- test/xUnit/xUnit.tests.csproj | 2 +- tools/cgmanifest.json | 148 +++++++----------- 16 files changed, 80 insertions(+), 120 deletions(-) diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index 8541f8ba795..267f0bda7d6 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -4,7 +4,7 @@ "quality": "daily", "qualityFallback": "preview", "packageVersionPattern": "9.0.0-preview.6", - "sdkImageVersion": "10.0.100-rc.1.25451.107", + "sdkImageVersion": "10.0.100", "nextChannel": "9.0.0-preview.7", "azureFeed": "", "sdkImageOverride": "" diff --git a/global.json b/global.json index 83e6a8b43e0..376af49c07f 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.100-rc.2.25502.107" + "version": "10.0.100" } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index cc3760db39f..a338c175f14 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index f5f6fede1af..ef949508346 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index da15fe82350..1f9f342b259 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 228d6360022..45b4d8c0a0a 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 57994fb98f7..87f71daada9 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,19 +16,19 @@ - - + + - - + + - + diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index bef80519d56..48b4d8ff29c 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 114a4c09aaf..e929166b20d 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,12 +32,12 @@ - - - - - - + + + + + + diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj index d67f17c60f9..91566f64fb1 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index 3274f04acec..af3fc10ffce 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -9,7 +9,7 @@ - - + + diff --git a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj index de06b206f37..886b5f11826 100644 --- a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj +++ b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index d2004ea2fdd..15a6e49864c 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index 056f6810c71..1dddf99ba5e 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ - + diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 5cb4a1e48d8..0863a23d441 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -30,7 +30,7 @@ all - + diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json index 26936804614..d8165298b22 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest.json @@ -86,7 +86,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -126,7 +126,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -166,7 +166,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -176,7 +176,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -186,7 +186,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -206,7 +206,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -216,7 +216,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -226,7 +226,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -236,7 +236,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -246,7 +246,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -256,7 +256,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -266,7 +266,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -276,7 +276,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -286,7 +286,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -296,7 +296,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -306,7 +306,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -316,7 +316,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -326,7 +326,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -336,7 +336,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -356,7 +356,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -366,7 +366,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -376,7 +376,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -436,7 +436,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -446,7 +446,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -456,7 +456,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -466,7 +466,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -476,7 +476,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -486,7 +486,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -506,7 +506,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -516,7 +516,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -526,7 +526,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -536,7 +536,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -546,7 +546,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -556,7 +556,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -566,7 +566,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -576,7 +576,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -586,7 +586,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -596,17 +596,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "9.0.10" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Private.ServiceModel", - "Version": "4.10.3" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -616,7 +606,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -626,7 +616,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -636,7 +626,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -646,7 +636,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -656,7 +646,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -666,7 +656,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -675,8 +665,8 @@ "Component": { "Type": "nuget", "Nuget": { - "Name": "System.ServiceModel.Duplex", - "Version": "4.10.3" + "Name": "System.ServiceModel.Http", + "Version": "8.1.2" } }, "DevelopmentDependency": false @@ -685,8 +675,8 @@ "Component": { "Type": "nuget", "Nuget": { - "Name": "System.ServiceModel.Http", - "Version": "4.10.3" + "Name": "System.ServiceModel.NetFramingBase", + "Version": "8.1.2" } }, "DevelopmentDependency": false @@ -696,7 +686,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.NetTcp", - "Version": "4.10.3" + "Version": "8.1.2" } }, "DevelopmentDependency": false @@ -706,17 +696,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Primitives", - "Version": "4.10.3" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.ServiceModel.Security", - "Version": "4.10.3" + "Version": "8.1.2" } }, "DevelopmentDependency": false @@ -726,7 +706,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -736,7 +716,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -746,27 +726,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "9.0.10" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Text.Encoding.CodePages", - "Version": "9.0.10" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Threading.AccessControl", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false @@ -786,7 +746,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "9.0.10" + "Version": "10.0.0" } }, "DevelopmentDependency": false From d486365b246c29ad9bc2704452ead96083e93a40 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Tue, 18 Nov 2025 21:49:38 +0900 Subject: [PATCH 118/255] Fix Invoke-RestMethod to support read-only files in multipart form data (#26454) --- .../Common/WebRequestPSCmdlet.Common.cs | 2 +- .../WebCmdlets.Tests.ps1 | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index 7a784a1e495..f1a455974b9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -1995,7 +1995,7 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream /// The file to use for the private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file) { - StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); + StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)); result.Headers.ContentDisposition.FileName = file.Name; result.Headers.ContentDisposition.FileNameStar = file.Name; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 1ddb9f10ba9..7c0fffa5c4a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1727,6 +1727,26 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Files[0].Content | Should -Match $file1Contents } + It "Verifies Invoke-WebRequest -Form supports read-only file values" { + # Create a read-only test file + $readOnlyFile = Join-Path $TestDrive "readonly-test.txt" + "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true + + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $response = Invoke-WebRequest -Uri $uri -Form $form -Method 'POST' + $result = $response.Content | ConvertFrom-Json + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -BeExactly "TestFile" + $result.Files[0].FileName | Should -BeExactly "readonly-test.txt" + $result.Files[0].ContentType | Should -BeExactly 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" + } + It "Verifies Invoke-WebRequest -Form sets Content-Disposition FileName and FileNameStar." { $ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("attachment") $ContentDisposition.FileName = $fileName @@ -3568,6 +3588,25 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Files[0].Content | Should -Match $file1Contents } + It "Verifies Invoke-RestMethod -Form supports read-only file values" { + # Create a read-only test file + $readOnlyFile = Join-Path $TestDrive "readonly-test.txt" + "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true + + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $result = Invoke-RestMethod -Uri $uri -Form $form -Method 'POST' + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -Be "TestFile" + $result.Files[0].FileName | Should -Be "readonly-test.txt" + $result.Files[0].ContentType | Should -Be 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" + } + It "Verifies Invoke-RestMethod -Form sets Content-Disposition FileName and FileNameStar." { $ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("attachment") $ContentDisposition.FileName = $fileName From 6928efb4c5224805c9d6f9fef5a20e26a20fd89c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:47:44 -0800 Subject: [PATCH 119/255] Fix GitHub API rate limit errors in test actions (#26489) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .github/actions/test/nix/action.yml | 10 ++++++++-- .github/actions/test/windows/action.yml | 10 ++++++++-- .github/workflows/linux-ci.yml | 4 ++++ .github/workflows/macos-ci.yml | 4 ++++ .github/workflows/windows-ci.yml | 4 ++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml index b8c1778974c..7f68e71c1f5 100644 --- a/.github/actions/test/nix/action.yml +++ b/.github/actions/test/nix/action.yml @@ -14,6 +14,9 @@ inputs: required: false default: ctrf type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true runs: using: composite @@ -64,7 +67,10 @@ runs: shell: pwsh run: |- Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 - $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" + $headers = @{ + Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}" + } + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 $latestVersion = $latestRelease.tag_name.TrimStart("v") Write-Host "Latest DSC Version: $latestVersion" @@ -78,7 +84,7 @@ runs: $tempPath = Get-GWTempPath - Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath/DSC.tar.gz" -Verbose + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath/DSC.tar.gz" -Verbose -Headers $headers New-Item -ItemType Directory -Path "$tempPath/DSC" -Force -Verbose tar xvf "$tempPath/DSC.tar.gz" -C "$tempPath/DSC" $dscRoot = "$tempPath/DSC" diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml index 923e9606e59..2c41f6aac5c 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -14,6 +14,9 @@ inputs: required: false default: ctrf type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true runs: using: composite @@ -47,7 +50,10 @@ runs: shell: pwsh run: |- Import-Module .\.github\workflows\GHWorkflowHelper\GHWorkflowHelper.psm1 - $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" + $headers = @{ + Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}" + } + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 $latestVersion = $latestRelease.tag_name.TrimStart("v") Write-Host "Latest DSC Version: $latestVersion" @@ -55,7 +61,7 @@ runs: $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url Write-Host "Download URL: $downloadUrl" $tempPath = Get-GWTempPath - Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath\DSC.zip" + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath\DSC.zip" -Headers $headers $null = New-Item -ItemType Directory -Path "$tempPath\DSC" -Force Expand-Archive -Path "$tempPath\DSC.zip" -DestinationPath "$tempPath\DSC" -Force diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 2058bd61568..030139b6c30 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -111,6 +111,7 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux_test_elevated_ci: name: Linux Elevated CI needs: @@ -128,6 +129,7 @@ jobs: with: purpose: ElevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux_test_unelevated_others: name: Linux Unelevated Others needs: @@ -145,6 +147,7 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux_test_elevated_others: name: Linux Elevated Others needs: @@ -162,6 +165,7 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} xunit_tests: name: xUnit Tests needs: diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index bdb7c128d12..721077a6b70 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -93,6 +93,7 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} macos_test_elevated_ci: name: macOS Elevated CI needs: @@ -110,6 +111,7 @@ jobs: with: purpose: ElevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} macos_test_unelevated_others: name: macOS Unelevated Others needs: @@ -127,6 +129,7 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} macos_test_elevated_others: name: macOS Elevated Others needs: @@ -144,6 +147,7 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} xunit_tests: name: xUnit Tests needs: diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 582860de34c..4f774fcdc9b 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -97,6 +97,7 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} windows_test_elevated_ci: name: Windows Elevated CI needs: @@ -114,6 +115,7 @@ jobs: with: purpose: ElevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} windows_test_unelevated_others: name: Windows Unelevated Others needs: @@ -131,6 +133,7 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} windows_test_elevated_others: name: Windows Elevated Others needs: @@ -148,6 +151,7 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} xunit_tests: name: xUnit Tests needs: From 230970bb5dcf16939533f6038e02c484aa50998f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:07:46 -0800 Subject: [PATCH 120/255] Bump github/codeql-action from 4.31.3 to 4.31.4 (#26484) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 6b9878be9ea..9ed85e11e23 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 + uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 + uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 4c502ed38a3..3f5951951e1 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 + uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v3.29.5 with: sarif_file: results.sarif From 108eb857bb2beed653556857f1fb551bdbdd1f0a Mon Sep 17 00:00:00 2001 From: yotsuda Date: Fri, 21 Nov 2025 21:06:23 +0900 Subject: [PATCH 121/255] Correct handling of explicit -[Operator]:$false parameter values in Where-Object (#26485) --- .../engine/InternalCommands.cs | 157 +++++++++--- .../Where-Object.Tests.ps1 | 238 ++++++++++++++++++ 2 files changed, 363 insertions(+), 32 deletions(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 844a965ac1e..4e50e2d130f 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -1441,8 +1441,11 @@ public SwitchParameter EQ set { - _binaryOperator = TokenKind.Ieq; - _forceBooleanEvaluation = false; + if (value) + { + _binaryOperator = TokenKind.Ieq; + _forceBooleanEvaluation = false; + } } } @@ -1459,7 +1462,10 @@ public SwitchParameter CEQ set { - _binaryOperator = TokenKind.Ceq; + if (value) + { + _binaryOperator = TokenKind.Ceq; + } } } @@ -1477,7 +1483,10 @@ public SwitchParameter NE set { - _binaryOperator = TokenKind.Ine; + if (value) + { + _binaryOperator = TokenKind.Ine; + } } } @@ -1494,7 +1503,10 @@ public SwitchParameter CNE set { - _binaryOperator = TokenKind.Cne; + if (value) + { + _binaryOperator = TokenKind.Cne; + } } } @@ -1512,7 +1524,10 @@ public SwitchParameter GT set { - _binaryOperator = TokenKind.Igt; + if (value) + { + _binaryOperator = TokenKind.Igt; + } } } @@ -1529,7 +1544,10 @@ public SwitchParameter CGT set { - _binaryOperator = TokenKind.Cgt; + if (value) + { + _binaryOperator = TokenKind.Cgt; + } } } @@ -1547,7 +1565,10 @@ public SwitchParameter LT set { - _binaryOperator = TokenKind.Ilt; + if (value) + { + _binaryOperator = TokenKind.Ilt; + } } } @@ -1564,7 +1585,10 @@ public SwitchParameter CLT set { - _binaryOperator = TokenKind.Clt; + if (value) + { + _binaryOperator = TokenKind.Clt; + } } } @@ -1582,7 +1606,10 @@ public SwitchParameter GE set { - _binaryOperator = TokenKind.Ige; + if (value) + { + _binaryOperator = TokenKind.Ige; + } } } @@ -1599,7 +1626,10 @@ public SwitchParameter CGE set { - _binaryOperator = TokenKind.Cge; + if (value) + { + _binaryOperator = TokenKind.Cge; + } } } @@ -1617,7 +1647,10 @@ public SwitchParameter LE set { - _binaryOperator = TokenKind.Ile; + if (value) + { + _binaryOperator = TokenKind.Ile; + } } } @@ -1634,7 +1667,10 @@ public SwitchParameter CLE set { - _binaryOperator = TokenKind.Cle; + if (value) + { + _binaryOperator = TokenKind.Cle; + } } } @@ -1652,7 +1688,10 @@ public SwitchParameter Like set { - _binaryOperator = TokenKind.Ilike; + if (value) + { + _binaryOperator = TokenKind.Ilike; + } } } @@ -1669,7 +1708,10 @@ public SwitchParameter CLike set { - _binaryOperator = TokenKind.Clike; + if (value) + { + _binaryOperator = TokenKind.Clike; + } } } @@ -1687,7 +1729,10 @@ public SwitchParameter NotLike set { - _binaryOperator = TokenKind.Inotlike; + if (value) + { + _binaryOperator = TokenKind.Inotlike; + } } } @@ -1704,7 +1749,10 @@ public SwitchParameter CNotLike set { - _binaryOperator = TokenKind.Cnotlike; + if (value) + { + _binaryOperator = TokenKind.Cnotlike; + } } } @@ -1722,7 +1770,10 @@ public SwitchParameter Match set { - _binaryOperator = TokenKind.Imatch; + if (value) + { + _binaryOperator = TokenKind.Imatch; + } } } @@ -1739,7 +1790,10 @@ public SwitchParameter CMatch set { - _binaryOperator = TokenKind.Cmatch; + if (value) + { + _binaryOperator = TokenKind.Cmatch; + } } } @@ -1757,7 +1811,10 @@ public SwitchParameter NotMatch set { - _binaryOperator = TokenKind.Inotmatch; + if (value) + { + _binaryOperator = TokenKind.Inotmatch; + } } } @@ -1774,7 +1831,10 @@ public SwitchParameter CNotMatch set { - _binaryOperator = TokenKind.Cnotmatch; + if (value) + { + _binaryOperator = TokenKind.Cnotmatch; + } } } @@ -1792,7 +1852,10 @@ public SwitchParameter Contains set { - _binaryOperator = TokenKind.Icontains; + if (value) + { + _binaryOperator = TokenKind.Icontains; + } } } @@ -1809,7 +1872,10 @@ public SwitchParameter CContains set { - _binaryOperator = TokenKind.Ccontains; + if (value) + { + _binaryOperator = TokenKind.Ccontains; + } } } @@ -1827,7 +1893,10 @@ public SwitchParameter NotContains set { - _binaryOperator = TokenKind.Inotcontains; + if (value) + { + _binaryOperator = TokenKind.Inotcontains; + } } } @@ -1844,7 +1913,10 @@ public SwitchParameter CNotContains set { - _binaryOperator = TokenKind.Cnotcontains; + if (value) + { + _binaryOperator = TokenKind.Cnotcontains; + } } } @@ -1862,7 +1934,10 @@ public SwitchParameter In set { - _binaryOperator = TokenKind.In; + if (value) + { + _binaryOperator = TokenKind.In; + } } } @@ -1879,7 +1954,10 @@ public SwitchParameter CIn set { - _binaryOperator = TokenKind.Cin; + if (value) + { + _binaryOperator = TokenKind.Cin; + } } } @@ -1897,7 +1975,10 @@ public SwitchParameter NotIn set { - _binaryOperator = TokenKind.Inotin; + if (value) + { + _binaryOperator = TokenKind.Inotin; + } } } @@ -1914,7 +1995,10 @@ public SwitchParameter CNotIn set { - _binaryOperator = TokenKind.Cnotin; + if (value) + { + _binaryOperator = TokenKind.Cnotin; + } } } @@ -1931,7 +2015,10 @@ public SwitchParameter Is set { - _binaryOperator = TokenKind.Is; + if (value) + { + _binaryOperator = TokenKind.Is; + } } } @@ -1948,7 +2035,10 @@ public SwitchParameter IsNot set { - _binaryOperator = TokenKind.IsNot; + if (value) + { + _binaryOperator = TokenKind.IsNot; + } } } @@ -1965,7 +2055,10 @@ public SwitchParameter Not set { - _binaryOperator = TokenKind.Not; + if (value) + { + _binaryOperator = TokenKind.Not; + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 index c12a1bdb516..eae7d9951a3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 @@ -142,4 +142,242 @@ Describe "Where-Object" -Tags "CI" { $Result[0] | Should -Be $dynObj $Result[1] | Should -Be $dynObj } + + Context "Switch parameter explicit `$false handling" { + BeforeAll { + $testData = @( + [PSCustomObject]@{ Name = 'Alice'; Age = 25; City = 'New York' } + [PSCustomObject]@{ Name = 'Bob'; Age = 30; City = 'Boston' } + [PSCustomObject]@{ Name = 'Charlie'; Age = 35; City = 'Chicago' } + ) + } + + It "-EQ:`$false should behave like default boolean evaluation" { + # With -EQ:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" is equivalent to "... | Where-Object {$true -eq $_.Name}" + # Non-empty strings are truthy + $result = $testData | Where-Object Name -EQ:$false + $result | Should -HaveCount 3 + } + + It "-GT:`$false should behave like default boolean evaluation" { + # With -GT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -GT:$false + $result | Should -HaveCount 3 + } + + It "-LT:`$false should behave like default boolean evaluation" { + # With -LT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -LT:$false + $result | Should -HaveCount 3 + } + + It "-NE:`$false should behave like default boolean evaluation" { + # With -NE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NE:$false + $result | Should -HaveCount 3 + } + + It "-GE:`$false should behave like default boolean evaluation" { + # With -GE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -GE:$false + $result | Should -HaveCount 3 + } + + It "-LE:`$false should behave like default boolean evaluation" { + # With -LE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -LE:$false + $result | Should -HaveCount 3 + } + + It "-NotLike:`$false should behave like default boolean evaluation" { + # With -NotLike:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -NotLike:$false + $result | Should -HaveCount 3 + } + + It "-NotMatch:`$false should behave like default boolean evaluation" { + # With -NotMatch:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NotMatch:$false + $result | Should -HaveCount 3 + } + + It "-NotContains:`$false should behave like default boolean evaluation" { + # With -NotContains:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NotContains:$false + $result | Should -HaveCount 3 + } + + It "-NotIn:`$false should behave like default boolean evaluation" { + # With -NotIn:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NotIn:$false + $result | Should -HaveCount 3 + } + + It "-IsNot:`$false should behave like default boolean evaluation" { + # With -IsNot:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -IsNot:$false + $result | Should -HaveCount 3 + } + + It "-Like:`$false should behave like default boolean evaluation" { + # With -Like:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -Like:$false + $result | Should -HaveCount 3 + } + + It "-Match:`$false should behave like default boolean evaluation" { + # With -Match:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -Match:$false + $result | Should -HaveCount 3 + } + + It "-Contains:`$false should behave like default boolean evaluation" { + # With -Contains:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" checks if Name property is truthy + $result = $testData | Where-Object Name -Contains:$false + $result | Should -HaveCount 3 + } + + It "-In:`$false should behave like default boolean evaluation" { + # With -In:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" checks if Name property is truthy + $result = $testData | Where-Object Name -In:$false + $result | Should -HaveCount 3 + } + + It "-Is:`$false should behave like default boolean evaluation" { + # With -Is:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" checks if Name property is truthy + $result = $testData | Where-Object Name -Is:$false + $result | Should -HaveCount 3 + } + + It "-Not:`$false should behave like default boolean evaluation" { + $testData2 = @( + [PSCustomObject]@{ Name = ''; Value = 1 } + [PSCustomObject]@{ Name = 'Test'; Value = 2 } + ) + # With -Not:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" returns objects where Name is truthy (non-empty) + $result = $testData2 | Where-Object Name -Not:$false + $result | Should -HaveCount 1 + $result.Name | Should -Be 'Test' + } + + It "-GT:`$false with -Value should throw an error requiring an operator" { + # When a switch parameter is set to $false with -Value specified, + # it should throw an error because no valid operator is active + { $testData | Where-Object Age -GT:$false -Value 25 } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-EQ:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -EQ:$false with -Value + { $testData | Where-Object Name -EQ:$false -Value 'Alice' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-Like:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -Like:$false with -Value + { $testData | Where-Object Name -Like:$false -Value 'A*' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-CEQ:`$false should behave like default boolean evaluation" { + # With -CEQ:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CEQ:$false + $result | Should -HaveCount 3 + } + + It "-CNE:`$false should behave like default boolean evaluation" { + # With -CNE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNE:$false + $result | Should -HaveCount 3 + } + + It "-CGT:`$false should behave like default boolean evaluation" { + # With -CGT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CGT:$false + $result | Should -HaveCount 3 + } + + It "-CLT:`$false should behave like default boolean evaluation" { + # With -CLT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CLT:$false + $result | Should -HaveCount 3 + } + + It "-CGE:`$false should behave like default boolean evaluation" { + # With -CGE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CGE:$false + $result | Should -HaveCount 3 + } + + It "-CLE:`$false should behave like default boolean evaluation" { + # With -CLE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CLE:$false + $result | Should -HaveCount 3 + } + + It "-CLike:`$false should behave like default boolean evaluation" { + # With -CLike:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -CLike:$false + $result | Should -HaveCount 3 + } + + It "-CNotLike:`$false should behave like default boolean evaluation" { + # With -CNotLike:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -CNotLike:$false + $result | Should -HaveCount 3 + } + + It "-CMatch:`$false should behave like default boolean evaluation" { + # With -CMatch:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CMatch:$false + $result | Should -HaveCount 3 + } + + It "-CNotMatch:`$false should behave like default boolean evaluation" { + # With -CNotMatch:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNotMatch:$false + $result | Should -HaveCount 3 + } + + It "-CContains:`$false should behave like default boolean evaluation" { + # With -CContains:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CContains:$false + $result | Should -HaveCount 3 + } + + It "-CNotContains:`$false should behave like default boolean evaluation" { + # With -CNotContains:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNotContains:$false + $result | Should -HaveCount 3 + } + + It "-CIn:`$false should behave like default boolean evaluation" { + # With -CIn:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CIn:$false + $result | Should -HaveCount 3 + } + + It "-CNotIn:`$false should behave like default boolean evaluation" { + # With -CNotIn:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNotIn:$false + $result | Should -HaveCount 3 + } + + It "-CEQ:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -CEQ:$false with -Value + { $testData | Where-Object Name -CEQ:$false -Value 'Alice' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-CGT:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -CGT:$false with -Value + { $testData | Where-Object Age -CGT:$false -Value 25 } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-CLike:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -CLike:$false with -Value + { $testData | Where-Object Name -CLike:$false -Value 'A*' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + } } From fb6403d4da3e9379f7350c2d6b1d22d732698e76 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Fri, 21 Nov 2025 21:12:28 +0900 Subject: [PATCH 122/255] Respect -Repeat/-MtuSize/-Traceroute:$false in Test-Connection (#26479) --- .../management/TestConnectionCommand.cs | 44 +++++++++---------- .../Test-Connection.Tests.ps1 | 29 ++++++++++++ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 17c9ab9a5d3..2a4a453f8f7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -242,14 +242,13 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// protected override void BeginProcessing() { - switch (ParameterSetName) + if (Repeat) { - case RepeatPingParameterSet: - Count = int.MaxValue; - break; - case TcpPortParameterSet: - SetCountForTcpTest(); - break; + Count = int.MaxValue; + } + else if (ParameterSetName == TcpPortParameterSet) + { + SetCountForTcpTest(); } } @@ -265,21 +264,22 @@ protected override void ProcessRecord() foreach (var targetName in TargetName) { - switch (ParameterSetName) + if (MtuSize) + { + ProcessMTUSize(targetName); + } + else if (Traceroute) + { + ProcessTraceroute(targetName); + } + else if (ParameterSetName == TcpPortParameterSet) + { + ProcessConnectionByTCPPort(targetName); + } + else { - case DefaultPingParameterSet: - case RepeatPingParameterSet: - ProcessPing(targetName); - break; - case MtuSizeDetectParameterSet: - ProcessMTUSize(targetName); - break; - case TraceRouteParameterSet: - ProcessTraceroute(targetName); - break; - case TcpPortParameterSet: - ProcessConnectionByTCPPort(targetName); - break; + // None of the switch parameters are true: handle default ping or -Repeat + ProcessPing(targetName); } } } @@ -298,7 +298,7 @@ protected override void StopProcessing() private void SetCountForTcpTest() { - if (Repeat.IsPresent) + if (Repeat) { Count = int.MaxValue; } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 index 2138a447581..ad01b461b55 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 @@ -258,6 +258,15 @@ Describe "Test-Connection" -tags "CI", "RequireSudoOnUnix" { $pingResults.Where( { $_.Status -eq 'Success' }, 'Default', 1 ).BufferSize | Should -Be 32 } } + + It "Repeat with explicit false should behave like default ping" { + # -Repeat:$false should behave the same as not specifying -Repeat + $pingResults = Test-Connection $targetAddress -Repeat:$false + + # Should get exactly 4 pings (the default Count value) + $pingResults.Count | Should -Be 4 + $pingResults[0].Address | Should -BeExactly $targetAddress + } } Context "MTUSizeDetect" { @@ -291,6 +300,16 @@ Describe "Test-Connection" -tags "CI", "RequireSudoOnUnix" { $result | Should -BeOfType Int32 $result | Should -BeGreaterThan 0 } + + It "MtuSize with explicit false should behave like default ping" { + # -MtuSize:$false should behave the same as not specifying -MtuSize (default ping) + $pingResults = Test-Connection $targetAddress -MtuSize:$false + + # Should return PingStatus, not PingMtuStatus + $pingResults[0] | Should -BeOfType Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus + # Should get 4 pings (the default Count value) + $pingResults.Count | Should -Be 4 + } } Context "TraceRoute" { @@ -332,6 +351,16 @@ Describe "Test-Connection" -tags "CI", "RequireSudoOnUnix" { $results.Hostname | Should -Not -BeNullOrEmpty } + + It "Traceroute with explicit false should behave like default ping" { + # -Traceroute:$false should behave the same as not specifying -Traceroute (default ping) + $pingResults = Test-Connection $targetAddress -Traceroute:$false + + # Should return PingStatus, not TraceStatus + $pingResults[0] | Should -BeOfType Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus + # Should get 4 pings (the default Count value) + $pingResults.Count | Should -Be 4 + } } } From e81985d8f2b32f4bad0684749d29dbe3d5c074e1 Mon Sep 17 00:00:00 2001 From: yotsuda Date: Sat, 22 Nov 2025 02:50:32 +0900 Subject: [PATCH 123/255] Respect -UseWindowsPowerShell:$false in New-PSSession (#26469) --- .../remoting/commands/newrunspacecommand.cs | 11 ++++- .../engine/Remoting/PSSession.Tests.ps1 | 45 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index 8986def5a6d..60ca0c35e50 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -267,7 +267,16 @@ protected override void ProcessRecord() case NewPSSessionCommand.UseWindowsPowerShellParameterSet: { - remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + if (UseWindowsPowerShell) + { + remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + } + else + { + // When -UseWindowsPowerShell:$false is explicitly specified, + // fall back to the default ComputerName parameter set behavior + goto case NewPSSessionCommand.ComputerNameParameterSet; + } } break; diff --git a/test/powershell/engine/Remoting/PSSession.Tests.ps1 b/test/powershell/engine/Remoting/PSSession.Tests.ps1 index 689e587ddb1..317ed4af734 100644 --- a/test/powershell/engine/Remoting/PSSession.Tests.ps1 +++ b/test/powershell/engine/Remoting/PSSession.Tests.ps1 @@ -91,3 +91,48 @@ Describe "SkipCACheck and SkipCNCheck PSSession options are required for New-PSS $er.Exception.ErrorCode | Should -Be $expectedErrorCode } } + +Describe "New-PSSession -UseWindowsPowerShell switch parameter" -Tag "CI" { + + BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + + if (-not $IsWindows) { + $PSDefaultParameterValues['it:skip'] = $true + } + } + + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + + It "Should respect explicit -UseWindowsPowerShell:`$false parameter value" { + # Test 1: -UseWindowsPowerShell:$true should create a Windows PowerShell 5.1 session + $sessionWithTrue = $null + try { + { $script:sessionWithTrue = New-PSSession -UseWindowsPowerShell:$true } | Should -Not -Throw + $script:sessionWithTrue | Should -Not -BeNullOrEmpty + + # Verify it's Windows PowerShell 5.1 + $version = Invoke-Command -Session $script:sessionWithTrue -ScriptBlock { $PSVersionTable.PSVersion } + $version.Major | Should -Be 5 + $version.Minor | Should -Be 1 + } + finally { + if ($script:sessionWithTrue) { Remove-PSSession $script:sessionWithTrue -ErrorAction SilentlyContinue } + } + + # Test 2: -UseWindowsPowerShell:$false should use WSMan transport (not Process) + $sessionWithFalse = $null + try { + { $script:sessionWithFalse = New-PSSession -UseWindowsPowerShell:$false } | Should -Not -Throw + $script:sessionWithFalse | Should -Not -BeNullOrEmpty + + # Transport should be WSMan, not Process + $script:sessionWithFalse.Transport | Should -Be 'WSMan' + } + finally { + if ($script:sessionWithFalse) { Remove-PSSession $script:sessionWithFalse -ErrorAction SilentlyContinue } + } + } +} From bb712def99cc6b2c8ac5a7e3a4d3e84ad7c3845f Mon Sep 17 00:00:00 2001 From: yotsuda Date: Sat, 22 Nov 2025 02:56:07 +0900 Subject: [PATCH 124/255] Respect -Qualifier/-NoQualifier/-Leaf/-IsAbsolute:$false in Split-Path (#26474) --- .../commands/management/ParsePathCommand.cs | 220 +++++++++--------- .../Split-Path.Tests.ps1 | 30 ++- 2 files changed, 132 insertions(+), 118 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs index b6609f8b692..ca616301ebb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs @@ -289,137 +289,125 @@ protected override void ProcessRecord() { string result = null; - switch (ParameterSetName) + // Check switch parameters in order of specificity + if (IsAbsolute) { - case isAbsoluteSet: - string ignored; - bool isPathAbsolute = - SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored); + string ignored; + bool isPathAbsolute = + SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored); - WriteObject(isPathAbsolute); - continue; + WriteObject(isPathAbsolute); + continue; + } + else if (Qualifier) + { + int separatorIndex = pathsToParse[index].IndexOf(':'); - case qualifierSet: - int separatorIndex = pathsToParse[index].IndexOf(':'); + if (separatorIndex < 0) + { + FormatException e = + new( + StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index])); + WriteError( + new ErrorRecord( + e, + "ParsePathFormatError", // RENAME + ErrorCategory.InvalidArgument, + pathsToParse[index])); + continue; + } + else + { + // Check to see if it is provider or drive qualified - if (separatorIndex < 0) - { - FormatException e = - new( - StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index])); - WriteError( - new ErrorRecord( - e, - "ParsePathFormatError", // RENAME - ErrorCategory.InvalidArgument, - pathsToParse[index])); - continue; - } - else + if (SessionState.Path.IsProviderQualified(pathsToParse[index])) { - // Check to see if it is provider or drive qualified - - if (SessionState.Path.IsProviderQualified(pathsToParse[index])) - { - // The plus 2 is for the length of the provider separator - // which is "::" - - result = - pathsToParse[index].Substring( - 0, - separatorIndex + 2); - } - else - { - result = - pathsToParse[index].Substring( - 0, - separatorIndex + 1); - } - } + // The plus 2 is for the length of the provider separator + // which is "::" - break; - - case parentSet: - case literalPathSet: - try - { result = - SessionState.Path.ParseParent( - pathsToParse[index], - string.Empty, - CmdletProviderContext, - true); + pathsToParse[index].Substring( + 0, + separatorIndex + 2); } - catch (PSNotSupportedException) - { - // Since getting the parent path is not supported, - // the provider must be a container, item, or drive - // provider. Since the paths for these types of - // providers can't be split, asking for the parent - // is asking for an empty string. - result = string.Empty; - } - - break; - - case leafSet: - case leafBaseSet: - case extensionSet: - try + else { - // default handles leafSet result = - SessionState.Path.ParseChildName( - pathsToParse[index], - CmdletProviderContext, - true); - if (LeafBase) - { - result = System.IO.Path.GetFileNameWithoutExtension(result); - } - else if (Extension) - { - result = System.IO.Path.GetExtension(result); - } + pathsToParse[index].Substring( + 0, + separatorIndex + 1); } - catch (PSNotSupportedException) + } + } + else if (Leaf || LeafBase || Extension) + { + try + { + result = + SessionState.Path.ParseChildName( + pathsToParse[index], + CmdletProviderContext, + true); + if (LeafBase) { - // Since getting the leaf part of a path is not supported, - // the provider must be a container, item, or drive - // provider. Since the paths for these types of - // providers can't be split, asking for the leaf - // is asking for the specified path back. - result = pathsToParse[index]; + result = System.IO.Path.GetFileNameWithoutExtension(result); } - catch (DriveNotFoundException driveNotFound) + else if (Extension) { - WriteError( - new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; - } - catch (ProviderNotFoundException providerNotFound) - { - WriteError( - new ErrorRecord( - providerNotFound.ErrorRecord, - providerNotFound)); - continue; + result = System.IO.Path.GetExtension(result); } - - break; - - case noQualifierSet: - result = RemoveQualifier(pathsToParse[index]); - break; - - default: - Dbg.Diagnostics.Assert( - false, - "Only a known parameter set should be called"); - break; + } + catch (PSNotSupportedException) + { + // Since getting the leaf part of a path is not supported, + // the provider must be a container, item, or drive + // provider. Since the paths for these types of + // providers can't be split, asking for the leaf + // is asking for the specified path back. + result = pathsToParse[index]; + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + continue; + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + continue; + } + } + else if (NoQualifier) + { + result = RemoveQualifier(pathsToParse[index]); + } + else + { + // None of the switch parameters are true: default to -Parent behavior + try + { + result = + SessionState.Path.ParseParent( + pathsToParse[index], + string.Empty, + CmdletProviderContext, + true); + } + catch (PSNotSupportedException) + { + // Since getting the parent path is not supported, + // the provider must be a container, item, or drive + // provider. Since the paths for these types of + // providers can't be split, asking for the parent + // is asking for an empty string. + result = string.Empty; + } } if (result != null) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 index b9537a4a961..e34126b101f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 @@ -96,7 +96,33 @@ Describe "Split-Path" -Tags "CI" { Split-Path -Parent "\\server1\share1" | Should -BeExactly "${dirSep}${dirSep}server1" } - It 'Does not split a drive leter'{ - Split-Path -Path 'C:\' | Should -BeNullOrEmpty + It 'Does not split a drive letter' { + Split-Path -Path 'C:\' | Should -BeNullOrEmpty + } + + It "Should handle explicit -Qualifier:`$false parameter value correctly" { + # When -Qualifier:$false is specified, it should behave like -Parent (default) + # For env:PATH, the parent is empty string + $result = Split-Path -Path "env:PATH" -Qualifier:$false + $result | Should -BeNullOrEmpty + } + + It "Should handle explicit -NoQualifier:`$false parameter value correctly" { + # When -NoQualifier:$false is specified, it should behave like -Parent (default) + # For env:PATH with no qualifier, we expect empty string (parent of PATH in env drive) + $result = Split-Path -Path "env:PATH" -NoQualifier:$false + $result | Should -BeNullOrEmpty + } + + It "Should handle explicit -Leaf:`$false parameter value correctly" { + # When -Leaf:$false is specified, it should behave like -Parent (default) + $dirSep = [string]([System.IO.Path]::DirectorySeparatorChar) + Split-Path -Path "/usr/bin" -Leaf:$false | Should -BeExactly "${dirSep}usr" + } + + It "Should handle explicit -IsAbsolute:`$false parameter value correctly" { + # When -IsAbsolute:$false is specified, it should behave like -Parent (default) + $dirSep = [string]([System.IO.Path]::DirectorySeparatorChar) + Split-Path -Path "fs:/usr/bin" -IsAbsolute:$false | Should -BeExactly "fs:${dirSep}usr" } } From e2075afafe25dd0c2d090ab4032dbacbbcd32b0c Mon Sep 17 00:00:00 2001 From: Jonathan Gilbert Date: Fri, 21 Nov 2025 16:08:16 -0600 Subject: [PATCH 125/255] Explain the parameter `-UseNuGetOrg` in build documentation (#26507) --- docs/building/linux.md | 2 ++ docs/building/macos.md | 2 ++ docs/building/windows-core.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/docs/building/linux.md b/docs/building/linux.md index bd7f9aadf69..6ccf12073e2 100644 --- a/docs/building/linux.md +++ b/docs/building/linux.md @@ -64,6 +64,8 @@ Import-Module ./build.psm1 Start-PSBuild -UseNuGetOrg ``` +> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead. + Congratulations! If everything went right, PowerShell is now built. The `Start-PSBuild` script will output the location of the executable: diff --git a/docs/building/macos.md b/docs/building/macos.md index 4f15e3fb547..7fd767ffa1c 100644 --- a/docs/building/macos.md +++ b/docs/building/macos.md @@ -37,3 +37,5 @@ We cannot do this for you in the build module due to #[847][]. Start a PowerShell session by running `pwsh`, and then use `Start-PSBuild -UseNuGetOrg` from the module. After building, PowerShell will be at `./src/powershell-unix/bin/Debug/net6.0/osx-x64/publish/pwsh`. + +> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead. diff --git a/docs/building/windows-core.md b/docs/building/windows-core.md index 48e5c951633..20774695fcd 100644 --- a/docs/building/windows-core.md +++ b/docs/building/windows-core.md @@ -61,6 +61,8 @@ Import-Module ./build.psm1 Start-PSBuild -Clean -PSModuleRestore -UseNuGetOrg ``` +> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead. + Congratulations! If everything went right, PowerShell is now built and executable as `./src/powershell-win-core/bin/Debug/net6.0/win7-x64/publish/pwsh.exe`. This location is of the form `./[project]/bin/[configuration]/[framework]/[rid]/publish/[binary name]`, From f4476c5226d0610373a5989c810dd0ee23da3607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:36:14 -0800 Subject: [PATCH 126/255] Bump actions/checkout from 5 to 6 (#26505) --- .github/workflows/analyze-reusable.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/labels.yml | 2 +- .github/workflows/linux-ci.yml | 18 +++++++++--------- .github/workflows/macos-ci.yml | 14 +++++++------- .github/workflows/scorecards.yml | 2 +- .github/workflows/verify-markdown-links.yml | 2 +- .github/workflows/windows-ci.yml | 12 ++++++------ .../workflows/windows-packaging-reusable.yml | 2 +- .github/workflows/xunit-tests.yml | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 9ed85e11e23..c1d812e533a 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: '0' diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index ac57bd87b5a..be2dd55df7d 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -22,7 +22,7 @@ jobs: # You can define any steps you want, and they will run before the agent starts. # If you do not check out your code, Copilot will do this for you. steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 1000 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index dadc6fd4370..dbef2e1c8b2 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: 'Dependency Review' uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index cd0a1d31726..61b5eebb88f 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Check out the repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Verify PR has label starting with 'cl-' id: verify-labels diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 030139b6c30..49d1c0a055f 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -57,7 +57,7 @@ jobs: packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false @@ -76,7 +76,7 @@ jobs: contents: read steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check for merge conflict markers uses: "./.github/actions/infrastructure/merge-conflict-checker" @@ -88,7 +88,7 @@ jobs: if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 @@ -103,7 +103,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Linux Unelevated CI @@ -121,7 +121,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Linux Elevated CI @@ -139,7 +139,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Linux Unelevated Others @@ -157,7 +157,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Linux Elevated Others @@ -181,7 +181,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1 @@ -255,7 +255,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Linux Packaging diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 721077a6b70..60596c75ed7 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -56,7 +56,7 @@ jobs: buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Change Detection id: filter @@ -71,7 +71,7 @@ jobs: if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Build @@ -85,7 +85,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: macOS Unelevated CI @@ -103,7 +103,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: macOS Elevated CI @@ -121,7 +121,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: macOS Unelevated Others @@ -139,7 +139,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: macOS Elevated Others @@ -166,7 +166,7 @@ jobs: - macos-15-large steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3f5951951e1..8df06cd9f14 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false diff --git a/.github/workflows/verify-markdown-links.yml b/.github/workflows/verify-markdown-links.yml index b26539eb0cf..df37ba3c513 100644 --- a/.github/workflows/verify-markdown-links.yml +++ b/.github/workflows/verify-markdown-links.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Verify markdown links id: verify diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 4f774fcdc9b..92bbf2f4c9e 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -60,7 +60,7 @@ jobs: packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Change Detection id: filter @@ -75,7 +75,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Build @@ -89,7 +89,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Windows Unelevated CI @@ -107,7 +107,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Windows Elevated CI @@ -125,7 +125,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Windows Unelevated Others @@ -143,7 +143,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 - name: Windows Elevated Others diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index 1f03aaf5944..f3a46cae908 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -36,7 +36,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index efd05b76045..940e1cb839c 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ inputs.runner_os }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1000 From 8948c417c0ceb0a4f954b51599294ac06881eb5b Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Sat, 22 Nov 2025 16:38:35 +0900 Subject: [PATCH 127/255] Make Export-Csv -Append and -NoHeader mutually exclusive (#26472) --- .../commands/utility/CsvCommands.cs | 8 ++++++++ .../resources/CsvCommandStrings.resx | 3 +++ .../Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index fa49c33d2ab..e84a79b99b6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -270,6 +270,14 @@ protected override void BeginProcessing() this.ThrowTerminatingError(errorRecord); } + // Validate that Append and NoHeader are not specified together. + if (Append && NoHeader) + { + InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyAppendAndNoHeader); + ErrorRecord errorRecord = new(exception, "CannotSpecifyBothAppendAndNoHeader", ErrorCategory.InvalidData, null); + this.ThrowTerminatingError(errorRecord); + } + _shouldProcess = ShouldProcess(Path); if (!_shouldProcess) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx index 0eff0d6f84f..8c5ded13465 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx @@ -157,4 +157,7 @@ EOF is reached. + + You must specify either the -Append or -NoHeader parameters, but not both. + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 index db5c6aff9e3..ea05d66d392 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 @@ -187,6 +187,10 @@ Describe "Export-Csv" -Tags "CI" { $results[1].PSObject.properties.Name | Should -Not -Contain 'third' } + It "Should throw when -Append and -NoHeader are specified together" { + { $P1 | Export-Csv -Path $testCsv -Append -NoHeader -ErrorAction Stop } | Should -Throw -ErrorId "CannotSpecifyBothAppendAndNoHeader,Microsoft.PowerShell.Commands.ExportCsvCommand" + } + It "First line should be #TYPE if -IncludeTypeInformation used and pstypenames object property is empty" { $object = [PSCustomObject]@{first = 1} $pstypenames = $object.pstypenames | ForEach-Object -Process {$_} From 70a6a80f6b88e5488b364fb077fd238b9085f6f1 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Sat, 22 Nov 2025 22:28:27 +0900 Subject: [PATCH 128/255] Add -Extension parameter to Join-Path cmdlet (#26482) --- .../commands/management/CombinePathCommand.cs | 21 ++++ .../Join-Path.Tests.ps1 | 98 +++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs index 71ef5df8f08..79b1c862bfd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs @@ -51,6 +51,21 @@ public class JoinPathCommand : CoreCommandWithCredentialsBase [Parameter] public SwitchParameter Resolve { get; set; } + /// + /// Gets or sets the extension to use for the resulting path. + /// If not specified, the original extension (if any) is preserved. + /// + /// Behavior: + /// - If the path has an existing extension, it will be replaced with the specified extension. + /// - If the path does not have an extension, the specified extension will be added. + /// - If an empty string is provided, any existing extension will be removed. + /// - A leading dot in the extension is optional; if omitted, one will be added automatically. + /// + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string Extension { get; set; } + #endregion Parameters #region Command code @@ -128,6 +143,12 @@ protected override void ProcessRecord() continue; } + // If Extension parameter is present it is not null due to [ValidateNotNull]. + if (Extension is not null) + { + joinedPath = System.IO.Path.ChangeExtension(joinedPath, Extension.Length == 0 ? null : Extension); + } + if (Resolve) { // Resolve the paths. The default API (GetResolvedPSPathFromPSPath) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 index a66bddd5e4f..9b0898fee90 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 @@ -84,4 +84,102 @@ Describe "Join-Path cmdlet tests" -Tags "CI" { $result = Join-Path -Path $Path -ChildPath $ChildPath $result | Should -BeExactly $ExpectedResult } + It "should handle extension parameter: " -TestCases @( + @{ + TestName = "change extension" + ChildPath = "file.txt" + Extension = ".log" + ExpectedChildPath = "file.log" + } + @{ + TestName = "add extension to file without extension" + ChildPath = "file" + Extension = ".txt" + ExpectedChildPath = "file.txt" + } + @{ + TestName = "extension without leading dot" + ChildPath = "file.txt" + Extension = "log" + ExpectedChildPath = "file.log" + } + @{ + TestName = "double extension with dot" + ChildPath = "file.txt" + Extension = ".tar.gz" + ExpectedChildPath = "file.tar.gz" + } + @{ + TestName = "double extension without dot" + ChildPath = "file.txt" + Extension = "tar.gz" + ExpectedChildPath = "file.tar.gz" + } + @{ + TestName = "remove extension with empty string" + ChildPath = "file.txt" + Extension = "" + ExpectedChildPath = "file" + } + @{ + TestName = "preserve dots in base name when removing extension with empty string" + ChildPath = "file...txt" + Extension = "" + ExpectedChildPath = "file.." + } + @{ + TestName = "replace only the last extension for files with multiple dots" + ChildPath = "file.backup.txt" + Extension = ".log" + ExpectedChildPath = "file.backup.log" + } + @{ + TestName = "preserve dots in base name when changing extension" + ChildPath = "file...txt" + Extension = ".md" + ExpectedChildPath = "file...md" + } + @{ + TestName = "add extension to directory-like path" + ChildPath = "subfolder" + Extension = ".log" + ExpectedChildPath = "subfolder.log" + } + ) { + param($TestName, $ChildPath, $Extension, $ExpectedChildPath) + $result = Join-Path -Path "folder" -ChildPath $ChildPath -Extension $Extension + $result | Should -BeExactly "folder${SepChar}${ExpectedChildPath}" + } + It "should handle extension parameter with multiple child path segments: " -TestCases @( + @{ + TestName = "change extension when joining multiple child path segments" + ChildPaths = @("subfolder", "file.txt") + Extension = ".log" + ExpectedPath = "folder${SepChar}subfolder${SepChar}file.log" + } + ) { + param($TestName, $ChildPaths, $Extension, $ExpectedPath) + $result = Join-Path -Path "folder" -ChildPath $ChildPaths -Extension $Extension + $result | Should -BeExactly $ExpectedPath + } + It "should change extension for multiple paths" { + $result = Join-Path -Path "folder1", "folder2" -ChildPath "file.txt" -Extension ".log" + $result.Count | Should -Be 2 + $result[0] | Should -BeExactly "folder1${SepChar}file.log" + $result[1] | Should -BeExactly "folder2${SepChar}file.log" + } + It "should resolve path when -Extension changes to existing file" { + New-Item -Path TestDrive:\testfile.log -ItemType File -Force | Out-Null + $result = Join-Path -Path TestDrive: -ChildPath "testfile.txt" -Extension ".log" -Resolve + $result | Should -BeLike "*testfile.log" + } + It "should throw error when -Extension changes to non-existing file with -Resolve" { + { Join-Path -Path TestDrive: -ChildPath "testfile.txt" -Extension ".nonexistent" -Resolve -ErrorAction Stop; Throw "Previous statement unexpectedly succeeded..." } | + Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.JoinPathCommand" + } + It "should accept Extension from pipeline by property name" { + $obj = [PSCustomObject]@{ Path = "folder"; ChildPath = "file.txt"; Extension = ".log" } + $result = $obj | Join-Path + $result | Should -BeExactly "folder${SepChar}file.log" + } } From 9ee3c61c26f63070ba72b7303384db7956137aa3 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 24 Nov 2025 12:05:03 -0800 Subject: [PATCH 129/255] Close pipe client handles after creating the child ssh process (#26491) --- .../remoting/common/RunspaceConnectionInfo.cs | 34 +++++++++---------- .../fanin/OutOfProcTransportManager.cs | 8 ++--- .../Remoting/SSHRemotingCmdlets.Tests.ps1 | 21 ++++++++++++ 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index b6913e0cc1c..242bde2ac53 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -2211,11 +2211,11 @@ internal int StartSSHProcess( CommandTypes.Application, SearchResolutionOptions.None, CommandOrigin.Internal, - context) as ApplicationInfo; + context); - if (cmdInfo != null) + if (cmdInfo is ApplicationInfo appInfo) { - filePath = cmdInfo.Path; + filePath = appInfo.Path; } } else @@ -2273,13 +2273,13 @@ internal int StartSSHProcess( // Subsystem powershell /usr/local/bin/pwsh -SSHServerMode -NoLogo -NoProfile // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified, so any file executed in the runspace would be in the user's local system/process or a system they have access to in which case restricted remoting security guidelines should be used. - System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(filePath); + ProcessStartInfo startInfo = new(filePath); // pass "-i identity_file" command line argument to ssh if KeyFilePath is set // if KeyFilePath is not set, then ssh will use IdentityFile / IdentityAgent from ssh_config if defined else none by default if (!string.IsNullOrEmpty(this.KeyFilePath)) { - if (!System.IO.File.Exists(this.KeyFilePath)) + if (!File.Exists(this.KeyFilePath)) { throw new FileNotFoundException( StringUtil.Format(RemotingErrorIdStrings.KeyFileNotFound, this.KeyFilePath)); @@ -2326,7 +2326,7 @@ internal int StartSSHProcess( // note that ssh expects IPv6 addresses to not be enclosed in square brackets so trim them if present startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-s {this.ComputerName.TrimStart('[').TrimEnd(']')} {this.Subsystem}")); - startInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(filePath); + startInfo.WorkingDirectory = Path.GetDirectoryName(filePath); startInfo.CreateNoWindow = true; startInfo.UseShellExecute = false; @@ -2580,7 +2580,7 @@ private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arr // Allocate the unmanaged array to hold each string pointer. // It needs to have an extra element to null terminate the array. arrPtr = (byte**)Marshal.AllocHGlobal(sizeof(IntPtr) * arrLength); - System.Diagnostics.Debug.Assert(arrPtr != null, "Invalid array ptr"); + Debug.Assert(arrPtr != null, "Invalid array ptr"); // Zero the memory so that if any of the individual string allocations fails, // we can loop through the array to free any that succeeded. @@ -2597,7 +2597,7 @@ private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arr byte[] byteArr = System.Text.Encoding.UTF8.GetBytes(arr[i]); arrPtr[i] = (byte*)Marshal.AllocHGlobal(byteArr.Length + 1); // +1 for null termination - System.Diagnostics.Debug.Assert(arrPtr[i] != null, "Invalid array ptr"); + Debug.Assert(arrPtr[i] != null, "Invalid array ptr"); Marshal.Copy(byteArr, 0, (IntPtr)arrPtr[i], byteArr.Length); // copy over the data from the managed byte array arrPtr[i][byteArr.Length] = (byte)'\0'; // null terminate @@ -2641,13 +2641,13 @@ internal static extern unsafe int ForkAndExecProcess( /// P-Invoking native APIs. /// private static int StartSSHProcessImpl( - System.Diagnostics.ProcessStartInfo startInfo, + ProcessStartInfo startInfo, out StreamWriter stdInWriterVar, out StreamReader stdOutReaderVar, out StreamReader stdErrReaderVar) { Exception ex = null; - System.Diagnostics.Process sshProcess = null; + Process sshProcess = null; // // These std pipe handles are bound to managed Reader/Writer objects and returned to the transport // manager object, which uses them for PSRP communication. The lifetime of these handles are then @@ -2668,7 +2668,7 @@ private static int StartSSHProcessImpl( catch (InvalidOperationException e) { ex = e; } catch (ArgumentException e) { ex = e; } catch (FileNotFoundException e) { ex = e; } - catch (System.ComponentModel.Win32Exception e) { ex = e; } + catch (Win32Exception e) { ex = e; } if ((ex != null) || (sshProcess == null) || @@ -2693,9 +2693,9 @@ private static int StartSSHProcessImpl( { if (stdInWriterVar != null) { stdInWriterVar.Dispose(); } else { stdInPipeServer.Dispose(); } - if (stdOutReaderVar != null) { stdInWriterVar.Dispose(); } else { stdOutPipeServer.Dispose(); } + if (stdOutReaderVar != null) { stdOutReaderVar.Dispose(); } else { stdOutPipeServer.Dispose(); } - if (stdErrReaderVar != null) { stdInWriterVar.Dispose(); } else { stdErrPipeServer.Dispose(); } + if (stdErrReaderVar != null) { stdErrReaderVar.Dispose(); } else { stdErrPipeServer.Dispose(); } throw; } @@ -2705,7 +2705,7 @@ private static int StartSSHProcessImpl( private static void KillSSHProcessImpl(int pid) { - using (var sshProcess = System.Diagnostics.Process.GetProcessById(pid)) + using (var sshProcess = Process.GetProcessById(pid)) { if ((sshProcess != null) && (sshProcess.Handle != IntPtr.Zero) && !sshProcess.HasExited) { @@ -2736,7 +2736,7 @@ private static Process CreateProcessWithRedirectedStd( SafeFileHandle stdInPipeClient = null; SafeFileHandle stdOutPipeClient = null; SafeFileHandle stdErrPipeClient = null; - string randomName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); + string randomName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()); try { @@ -2829,16 +2829,14 @@ private static Process CreateProcessWithRedirectedStd( catch (Exception) { stdInPipeServer?.Dispose(); - stdInPipeClient?.Dispose(); stdOutPipeServer?.Dispose(); - stdOutPipeClient?.Dispose(); stdErrPipeServer?.Dispose(); - stdErrPipeClient?.Dispose(); throw; } finally { + lpStartupInfo.Dispose(); lpProcessInformation.Dispose(); } } diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs index d9532c8691a..47ff6270dba 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -1733,7 +1733,7 @@ public override void CreateAsync() bool sshTerminated = false; try { - using (var sshProcess = System.Diagnostics.Process.GetProcessById(_sshProcessId)) + using (var sshProcess = Process.GetProcessById(_sshProcessId)) { sshTerminated = sshProcess == null || sshProcess.Handle == IntPtr.Zero || sshProcess.HasExited; } @@ -1847,7 +1847,7 @@ private void ProcessErrorThread(object state) // Messages in error stream from ssh are unreliable, and may just be warnings or // banner text. // So just report the messages but don't act on them. - System.Console.WriteLine(error); + Console.WriteLine(error); } catch (IOException) { } @@ -1907,10 +1907,10 @@ private void ProcessReaderThread(object state) break; } - if (data.StartsWith(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) + if (data.StartsWith(OutOfProcessTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) { // Error message from the server. - string errorData = data.Substring(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix.Length); + string errorData = data.Substring(OutOfProcessTextWriter.ErrorPrefix.Length); HandleErrorDataReceived(errorData); } else diff --git a/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 b/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 index 1568d284d0f..c1090bdc186 100644 --- a/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 +++ b/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 @@ -70,3 +70,24 @@ Describe "SSHConnection parameter hashtable type conversions" -Tags 'Feature', ' $err.FullyQualifiedErrorId | Should -Match 'PSSessionOpenFailed' } } + +Describe "No hangs when host doesn't exist" -Tags "CI" { + $testCases = @( + @{ + Name = 'Verifies no hang for New-PSSession with non-existing host name' + ScriptBlock = { New-PSSession -HostName "test-notexist" -UserName "test" -ErrorAction Stop } + FullyQualifiedErrorId = 'PSSessionOpenFailed' + }, + @{ + Name = 'Verifies no hang for Invoke-Command with non-existing host name' + ScriptBlock = { Invoke-Command -HostName "test-notexist" -UserName "test" -ScriptBlock { 1 } -ErrorAction Stop } + FullyQualifiedErrorId = 'PSSessionStateBroken' + } + ) + + It "" -TestCases $testCases { + param ($ScriptBlock, $FullyQualifiedErrorId) + + $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -ExceptionType 'System.Management.Automation.Remoting.PSRemotingTransportException' + } +} From eb2ead439a9d2f7366ef0fb5cad26235a77c8805 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 24 Nov 2025 14:38:23 -0800 Subject: [PATCH 130/255] Fix formatting to properly handle the `Reset` VT sequences that appear in the middle of a string (#26424) --- .../FormatAndOutput/common/ComplexWriter.cs | 37 +++++++-- .../FormatAndOutput/common/ListWriter.cs | 2 +- .../engine/Formatting/PSStyle.Tests.ps1 | 81 ++++++++++++++++++- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs index 61a3f962daf..a69ad41c965 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs @@ -9,7 +9,6 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Text; -using System.Text.RegularExpressions; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -317,6 +316,7 @@ internal struct GetWordsResult { internal string Word; internal string Delim; + internal bool VtResetAdded; } /// @@ -367,9 +367,19 @@ private static IEnumerable GetWords(string s) { var vtSpan = s.AsSpan(i, len); sb.Append(vtSpan); - vtSeqs.Append(vtSpan); - wordHasVtSeqs = true; + if (vtSpan.SequenceEqual(PSStyle.Instance.Reset)) + { + // The Reset sequence will void all previous VT sequences. + vtSeqs.Clear(); + wordHasVtSeqs = false; + } + else + { + vtSeqs.Append(vtSpan); + wordHasVtSeqs = true; + } + i += len - 1; continue; } @@ -390,15 +400,18 @@ private static IEnumerable GetWords(string s) if (delimiter is not null) { + bool vtResetAdded = false; if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset)) { + vtResetAdded = true; sb.Append(PSStyle.Instance.Reset); } var result = new GetWordsResult() { Word = sb.ToString(), - Delim = delimiter + Delim = delimiter, + VtResetAdded = vtResetAdded }; sb.Clear().Append(vtSeqs); @@ -611,7 +624,7 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe if (suffix is not null) { - wordToAdd = wordToAdd.EndsWith(resetStr) + wordToAdd = word.VtResetAdded ? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix) : wordToAdd + suffix; } @@ -760,9 +773,19 @@ internal static List SplitLines(string s) { var vtSpan = s.AsSpan(i, len); sb.Append(vtSpan); - vtSeqs.Append(vtSpan); - hasVtSeqs = true; + if (vtSpan.SequenceEqual(PSStyle.Instance.Reset)) + { + // The Reset sequence will void all previous VT sequences. + vtSeqs.Clear(); + hasVtSeqs = false; + } + else + { + vtSeqs.Append(vtSpan); + hasVtSeqs = true; + } + i += len - 1; continue; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs index 7598c6b2797..988b1133748 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs @@ -259,7 +259,7 @@ private void WriteSingleLineHelper(string prependString, string line, LineOutput _cachedBuilder.Append(headPadding).Append(str); } - if (str.Contains(ValueStringDecorated.ESC) && !str.EndsWith(reset)) + if (str.Contains(ValueStringDecorated.ESC) && !str.AsSpan().TrimEnd().EndsWith(reset, StringComparison.Ordinal)) { _cachedBuilder.Append(reset); } diff --git a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 index d8a124a2332..18baed17dd2 100644 --- a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 +++ b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 @@ -488,7 +488,7 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } - It "Word wrapping for string with escape sequences" { + It "Word wrapping for string with escape sequences (1)" { $expected = @" `e[32;1mLongDescription : `e[0m`e[33mPowerShell `e[0m `e[33mscripting `e[0m @@ -501,7 +501,46 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } - It "Splitting multi-line string with escape sequences" { + It "Word wrapping for string with escape sequences (2)" { + $expected = @" +`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m + scripting + language +"@ + $obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m scripting language" } + $obj | Format-List | Out-String -Width 35 | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Word wrapping for string with escape sequences (3)" { + $expected = @" +`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m + `e[32mscripting `e[0m + `e[32mlanguage`e[0m +"@ + $obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m `e[32mscripting language" } + $obj | Format-List | Out-String -Width 35 | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Word wrapping for string with escape sequences (4)" { + $expected = @" +`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m + `e[32mscripting`e[0m + language +"@ + $obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m `e[32mscripting`e[0m language" } + $obj | Format-List | Out-String -Width 35 | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Splitting multi-line string with escape sequences (1)" { $expected = @" `e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m `e[33mconsisting of a command-line shell and the associated scripting language`e[0m @@ -513,6 +552,30 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } + It "Splitting multi-line string with escape sequences (2)" { + $expected = @" +`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m + consisting of a command-line shell and the associated scripting language +"@ + $obj = [pscustomobject] @{ b = "`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m`nconsisting of a command-line shell and the associated scripting language" } + $obj | Format-List | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Splitting multi-line string with escape sequences (3)" { + $expected = @" +`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m + `e[32mconsisting of a command-line shell and the associated scripting language`e[0m +"@ + $obj = [pscustomobject] @{ b = "`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m`n`e[32mconsisting of a command-line shell and the associated scripting language" } + $obj | Format-List | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + It "Wrapping long word with escape sequences" { $expected = @" `e[32;1mb : `e[0m`e[33mC:\repos\PowerShell\src\powershell-w`e[0m @@ -525,4 +588,18 @@ Billy Bob… Senior DevOps … 13 $text = Get-Content $outFile -Raw $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } + + It "Format 'MatchInfo' object correctly" { + $expected = @" +`e[32;1mb : `e[0mmouclass `e[7mMouse`e[0m Class Driver Mouse Class Driver Kernel Manual Running OK TRUE FALSE 12,288 `e[0m + 32,768 0 C:\WINDOWS\system32\drivers\mouclass.sys 4,096 +"@ + + ## This string mimics the VT decorated string for a 'MatchInfo' object that matches the word 'mouse'. + $str = "mouclass `e[7mMouse`e[0m Class Driver Mouse Class Driver Kernel Manual Running OK TRUE FALSE 12,288 32,768 0 C:\WINDOWS\system32\drivers\mouclass.sys 4,096" + $obj = [pscustomobject] @{ b = $str } + $text = $obj | Format-List | Out-String -Width 150 + + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } } From b9ff1da7fc2250c247b9a2283a2fb7ed8dae9281 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:33:46 -0800 Subject: [PATCH 131/255] Bump github/codeql-action from 4.31.4 to 4.31.5 (#26527) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index c1d812e533a..8f4b31ac47a 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v3.29.5 + uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v3.29.5 + uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 8df06cd9f14..68eb0ea4b99 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v3.29.5 + uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 with: sarif_file: results.sarif From 01e36432fcfb3e5a0b801817a35ec5332ff7a199 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Wed, 26 Nov 2025 15:48:53 +0900 Subject: [PATCH 132/255] Add tab completion for $PSBoundParameters.Keys switch cases and access patterns (#26483) --- .../CommandCompletion/CompletionAnalysis.cs | 262 +++++++++++++++++- .../TabCompletion/TabCompletion.Tests.ps1 | 181 ++++++++++++ 2 files changed, 437 insertions(+), 6 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 8aafa939d84..a6b6f4170b3 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -251,8 +251,7 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs { Tuple fileConditionTuple; - var errorStatement = lastAst as ErrorStatementAst; - if (errorStatement != null && errorStatement.Flags != null && errorStatement.Kind != null && tokenBeforeCursor != null && + if (lastAst is ErrorStatementAst errorStatement && errorStatement.Flags is not null && errorStatement.Kind is not null && tokenBeforeCursor is not null && errorStatement.Kind.Kind.Equals(TokenKind.Switch) && errorStatement.Flags.TryGetValue("file", out fileConditionTuple)) { // Handle "switch -file " @@ -267,19 +266,234 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs return false; } - errorStatement = pipeline.Parent as ErrorStatementAst; - if (errorStatement == null || errorStatement.Kind == null || errorStatement.Flags == null) + if (pipeline.Parent is not ErrorStatementAst parentErrorStatement || parentErrorStatement.Kind is null || parentErrorStatement.Flags is null) { return false; } - return (errorStatement.Kind.Kind.Equals(TokenKind.Switch) && - errorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline); + return (parentErrorStatement.Kind.Kind.Equals(TokenKind.Switch) && + parentErrorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline); } return false; } + /// + /// Check if we should complete parameter names for switch cases on $PSBoundParameters.Keys + /// + private static List CompleteAgainstSwitchCaseCondition(CompletionContext completionContext) + { + var lastAst = completionContext.RelatedAsts.Last(); + + PipelineAst conditionPipeline = null; + Ast switchAst = null; + + // Check if we're in a switch statement (complete) or error statement (incomplete switch) + if (lastAst.Parent is SwitchStatementAst switchStatementAst) + { + // Verify that the lastAst is one of the clause conditions (not in the body) + bool isClauseCondition = switchStatementAst.Clauses.Any(clause => clause.Item1 == lastAst); + + if (!isClauseCondition) + { + return null; + } + + conditionPipeline = switchStatementAst.Condition as PipelineAst; + switchAst = switchStatementAst; + } + else + { + // Check for incomplete switch parsed as ErrorStatementAst + if (lastAst.Parent is not ErrorStatementAst errorStatementAst || errorStatementAst.Kind is null || + errorStatementAst.Kind.Kind != TokenKind.Switch) + { + return null; + } + + // For ErrorStatementAst, the case value is in Bodies, condition is in Conditions + bool isInBodies = errorStatementAst.Bodies != null && errorStatementAst.Bodies.Any(body => body == lastAst); + + if (!isInBodies) + { + return null; + } + + // Get the condition from ErrorStatementAst.Conditions + if (errorStatementAst.Conditions != null && errorStatementAst.Conditions.Count > 0) + { + conditionPipeline = errorStatementAst.Conditions[0] as PipelineAst; + } + switchAst = errorStatementAst; + } + + if (conditionPipeline == null || conditionPipeline.PipelineElements.Count != 1) + { + return null; + } + + if (conditionPipeline.PipelineElements[0] is not CommandExpressionAst commandExpressionAst) + { + return null; + } + + // Check if the expression is a member access on $PSBoundParameters.Keys + if (commandExpressionAst.Expression is not MemberExpressionAst memberExpressionAst) + { + return null; + } + + // Check if the target is $PSBoundParameters + if (memberExpressionAst.Expression is not VariableExpressionAst variableExpressionAst || + !variableExpressionAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Check if the member is "Keys" + if (memberExpressionAst.Member is not StringConstantExpressionAst memberNameAst || + !memberNameAst.Value.Equals("Keys", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Find the nearest param block by traversing up the AST + var paramBlockAst = FindNearestParamBlock(switchAst.Parent); + + if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0) + { + return null; + } + + // Generate completion results from parameter names + var wordToComplete = completionContext.WordToComplete ?? string.Empty; + return CreateParameterCompletionResults(paramBlockAst, wordToComplete); + } + + /// + /// Check if we should complete parameter names for $PSBoundParameters access patterns + /// Supports: $PSBoundParameters.ContainsKey('...'), $PSBoundParameters['...'], $PSBoundParameters.Remove('...') + /// + private static List CompleteAgainstPSBoundParametersAccess(CompletionContext completionContext) + { + var lastAst = completionContext.RelatedAsts.Last(); + + // Must be a string constant + if (lastAst is not StringConstantExpressionAst stringAst) + { + return null; + } + + ExpressionAst targetAst = null; + + // Check for method invocation: $PSBoundParameters.ContainsKey('...') or $PSBoundParameters.Remove('...') + if (lastAst.Parent is InvokeMemberExpressionAst invokeMemberAst) + { + if (invokeMemberAst.Member is StringConstantExpressionAst memberName && + (memberName.Value.Equals("ContainsKey", StringComparison.OrdinalIgnoreCase) || + memberName.Value.Equals("Remove", StringComparison.OrdinalIgnoreCase))) + { + targetAst = invokeMemberAst.Expression; + } + } + // Check for indexer: $PSBoundParameters['...'] + else if (lastAst.Parent is IndexExpressionAst indexAst) + { + targetAst = indexAst.Target; + } + + if (targetAst is null) + { + return null; + } + + // Check if target is $PSBoundParameters + if (targetAst is not VariableExpressionAst variableAst || + !variableAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Find the nearest param block + var paramBlockAst = FindNearestParamBlock(lastAst.Parent); + + if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0) + { + return null; + } + + // Generate completion results from parameter names + var wordToComplete = completionContext.WordToComplete ?? string.Empty; + + // Determine quote style based on the string constant type + string quoteChar = string.Empty; + if (stringAst.StringConstantType == StringConstantType.SingleQuoted) + { + quoteChar = "'"; + } + else if (stringAst.StringConstantType == StringConstantType.DoubleQuoted) + { + quoteChar = "\""; + } + + return CreateParameterCompletionResults(paramBlockAst, wordToComplete, quoteChar); + } + + /// + /// Finds the nearest ParamBlockAst by traversing up the AST hierarchy. + /// + /// The AST node to start searching from. + /// The nearest ParamBlockAst if found; otherwise, null. + private static ParamBlockAst FindNearestParamBlock(Ast startAst) + { + Ast current = startAst; + while (current != null) + { + if (current is FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst.Body?.ParamBlock; + } + else if (current is ScriptBlockAst scriptBlockAst) + { + var paramBlock = scriptBlockAst.ParamBlock; + if (paramBlock != null) + { + return paramBlock; + } + } + + current = current.Parent; + } + + return null; + } + + /// + /// Creates completion results from parameter names with optional quote wrapping. + /// + /// The parameter block containing parameters to complete. + /// The partial word to match against parameter names. + /// Optional quote character to wrap completion text (empty string for no quotes). + /// A list of completion results, or null if no matches found. + private static List CreateParameterCompletionResults( + ParamBlockAst paramBlockAst, + string wordToComplete, + string quoteChar = "") + { + var result = paramBlockAst.Parameters + .Select(parameter => parameter.Name.VariablePath.UserPath) + .Where(parameterName => parameterName.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + .Select(parameterName => + new CompletionResult( + quoteChar + parameterName + quoteChar, + parameterName, + CompletionResultType.ParameterValue, + parameterName)) + .ToList(); + + return result.Count > 0 ? result : null; + } + private static bool CompleteOperator(Token tokenAtCursor, Ast lastAst) { if (tokenAtCursor.Kind == TokenKind.Minus) @@ -516,6 +730,13 @@ internal List GetResultHelper(CompletionContext completionCont { // Handles quoted string inside index expression like: $PSVersionTable[""] completionContext.WordToComplete = (tokenAtCursor as StringToken).Value; + // Check for $PSBoundParameters indexer first + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + return CompletionCompleters.CompleteIndexExpression(completionContext, indexExpressionAst.Target); } @@ -1915,6 +2136,21 @@ private static List GetResultForString(CompletionContext compl string strValue = constantString != null ? constantString.Value : expandableString.Value; + // Check for switch case completion on $PSBoundParameters.Keys + completionContext.WordToComplete = strValue; + var switchCaseResult = CompleteAgainstSwitchCaseCondition(completionContext); + if (switchCaseResult != null && switchCaseResult.Count > 0) + { + return switchCaseResult; + } + + // Check for $PSBoundParameters access patterns (ContainsKey, indexer, Remove) + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + bool shouldContinue; List result = GetResultForEnumPropertyValueOfDSCResource(completionContext, strValue, ref replacementIndex, ref replacementLength, out shouldContinue); if (!shouldContinue || (result != null && result.Count > 0)) @@ -2076,6 +2312,20 @@ private static List GetResultForIdentifier(CompletionContext c var tokenAtCursorText = tokenAtCursor.Text; completionContext.WordToComplete = tokenAtCursorText; + // Check for switch case completion on $PSBoundParameters.Keys + var switchCaseResult = CompleteAgainstSwitchCaseCondition(completionContext); + if (switchCaseResult != null && switchCaseResult.Count > 0) + { + return switchCaseResult; + } + + // Check for $PSBoundParameters access patterns (ContainsKey, indexer, Remove) + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + if (lastAst.Parent is BreakStatementAst || lastAst.Parent is ContinueStatementAst) { return CompleteLoopLabel(completionContext); diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 046d00b442f..f8762a63929 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -4012,4 +4012,185 @@ Describe "WSMan Config Provider tab complete tests" -Tags Feature,RequireAdminOn # https://github.com/PowerShell/PowerShell/issues/4744 # TODO: move to test cases above once working } + + Context "Tab completion for switch cases on `$PSBoundParameters.Keys" { + It "Should complete parameter names in switch case for `$PSBoundParameters.Keys" { + $inputScript = @" +function Test-Func { + param( + [string]`$Param1, + [string]`$Param2, + [int]`$Count + ) + switch (`$PSBoundParameters.Keys) { + P + } +} +"@ + $cursorPosition = $inputScript.IndexOf("P", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "Param1" + $completionTexts[1] | Should -BeExactly "Param2" + } + + It "Should complete all parameter names when prefix matches single param" { + $inputScript = @" +function Test-Func { + param( + [string]`$Name, + [int]`$Value + ) + switch (`$PSBoundParameters.Keys) { + N + } +} +"@ + $cursorPosition = $inputScript.IndexOf("N", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Name" + } + + It "Should complete parameter names in scriptblock param" { + $inputScript = @" +`$sb = { + param( + [string]`$ScriptParam1, + [string]`$ScriptParam2 + ) + switch (`$PSBoundParameters.Keys) { + S + } +} +"@ + $cursorPosition = $inputScript.IndexOf("S", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "ScriptParam1" + $completionTexts[1] | Should -BeExactly "ScriptParam2" + } + } + + Context "Tab completion for `$PSBoundParameters access patterns" { + It "Should complete parameter names for ContainsKey method" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + if (`$PSBoundParameters.ContainsKey('P')) { } +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "'Param1'" + $completionTexts[1] | Should -BeExactly "'Param2'" + } + + It "Should complete parameter names for indexer access" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + `$value = `$PSBoundParameters['P'] +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "'Param1'" + $completionTexts[1] | Should -BeExactly "'Param2'" + } + + It "Should complete parameter names for Remove method" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + `$PSBoundParameters.Remove('C') +} +"@ + $cursorPosition = $inputScript.IndexOf("'C'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "'Count'" + } + + It "Should complete with double quotes when using double-quoted string" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2) + if (`$PSBoundParameters.ContainsKey("P")) { } +} +"@ + $cursorPosition = $inputScript.IndexOf('"P"') + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly '"Param1"' + $completionTexts[1] | Should -BeExactly '"Param2"' + } + + It "Should not complete for non-PSBoundParameters variable with indexer" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + `$value = `$hash['P'] +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with ContainsKey" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + if (`$hash.ContainsKey('P')) { } +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with Remove" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + `$hash.Remove('P') +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with double quotes" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + if (`$hash.ContainsKey("P")) { } +} +"@ + $cursorPosition = $inputScript.IndexOf('"P"') + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq '"Param1"' } + $paramCompletion | Should -BeNullOrEmpty + } + } } From ef0f9ae0ba9a67cab954d8d58c9868dd89569939 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:01:52 -0800 Subject: [PATCH 133/255] Bump github/codeql-action from 4.31.5 to 4.31.6 (#26554) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 8f4b31ac47a..21bc1ed1e92 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 + uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 + uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 68eb0ea4b99..3c322397db0 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 + uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 with: sarif_file: results.sarif From 11f91dfee3eeb7445be8680e0e59ef84f2cad2f5 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:16:56 +0000 Subject: [PATCH 134/255] Disable AMSI content logging in release (#26235) --- .../engine/runtime/Operations/MiscOps.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 50054f6501d..d584666ab62 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -3644,6 +3644,7 @@ internal static object[] GetSlice(IList list, int startIndex) internal static class MemberInvocationLoggingOps { +#if DEBUG private static readonly Lazy DumpLogAMSIContent = new Lazy( () => { object result = Environment.GetEnvironmentVariable("__PSDumpAMSILogContent"); @@ -3654,6 +3655,7 @@ internal static class MemberInvocationLoggingOps return false; } ); +#endif private static string ArgumentToString(object arg) { @@ -3708,20 +3710,24 @@ internal static void LogMemberInvocation(string targetName, string name, object[ string content = $"<{targetName}>.{name}({argsBuilder})"; +#if DEBUG if (DumpLogAMSIContent.Value) { Console.WriteLine("\n=== Amsi notification report content ==="); Console.WriteLine(content); } +#endif var success = AmsiUtils.ReportContent( name: contentName, content: content); +#if DEBUG if (DumpLogAMSIContent.Value) { Console.WriteLine($"=== Amsi notification report success: {success} ==="); } +#endif } catch (PSSecurityException) { @@ -3729,12 +3735,16 @@ internal static void LogMemberInvocation(string targetName, string name, object[ // must be propagated. throw; } +#pragma warning disable CS0168 // variable declared but never used catch (Exception ex) +#pragma warning restore CS0168 { +#if DEBUG if (DumpLogAMSIContent.Value) { Console.WriteLine($"!!! Amsi notification report exception: {ex} !!!"); } +#endif } } } From 5c57c3e19c85a03b9b493c14d9f4f6ec7b51bce6 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Wed, 3 Dec 2025 17:35:44 +0900 Subject: [PATCH 135/255] Fix NOTES section formatting in comment-based help (#26512) --- .../DefaultFormatters/Help_format_ps1xml.cs | 5 +--- .../ScriptHelp.NotesFormatting.Tests.ps1 | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs index 05f57286026..6825ec14e6c 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs @@ -680,10 +680,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Ful .StartFrame(leftIndent: 4) .AddPropertyExpressionBinding(@"title") .AddNewline() - .AddNewline() - .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11]) - .EndFrame() + .AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11]) .AddNewline() .EndFrame() .EndEntry() diff --git a/test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 b/test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 new file mode 100644 index 00000000000..77c0c65b1af --- /dev/null +++ b/test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Comment-based help NOTES section formatting' -Tags "CI", "Feature" { + It 'NOTES section should have single blank line after header and 4-space indentation' { + $helpText = Get-Help Remove-PSSession -Full | Out-String + $lines = $helpText -split "`r?`n" + + # Find NOTES section + $notesIndex = -1 + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($lines[$i] -match '^NOTES\s*$') { + $notesIndex = $i + break + } + } + + $notesIndex | Should -BeGreaterThan -1 -Because "NOTES section should exist" + + # The line after NOTES header should be blank + $lines[$notesIndex + 1] | Should -Match '^\s*$' -Because "There should be a blank line after NOTES header" + + # The second line after NOTES header should have content with 4-space indentation + $contentLine = $lines[$notesIndex + 2] + $contentLine | Should -Match '^[ ]{4}\S' -Because "Content should have exactly 4-space indentation" + } +} From 6311c7c2f6aa913408aa6fb2e619cd15c95ebd40 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Fri, 5 Dec 2025 13:54:52 +0900 Subject: [PATCH 136/255] Add -ExcludeProperty parameter to Format-* cmdlets (#26514) --- .../format-object/Format-Object.cs | 32 +++++++++-- .../format-wide/Format-Wide.cs | 35 +++++++++--- .../commands/utility/Select-Object.cs | 41 ------------- .../common/BaseFormattingCommand.cs | 37 ++++++++++-- .../common/BaseFormattingCommandParameters.cs | 5 ++ .../common/FormatViewGenerator.cs | 15 ++++- .../common/FormatViewGenerator_Complex.cs | 22 +++++-- .../common/FormatViewGenerator_List.cs | 8 ++- .../common/FormatViewGenerator_Table.cs | 57 ++++++++++--------- .../common/FormatViewGenerator_Wide.cs | 49 ++++++++-------- .../common/Utilities/Mshexpression.cs | 37 ++++++++++++ .../CommandCompletion/CompletionCompleters.cs | 3 +- .../Format-Custom.Tests.ps1 | 44 ++++++++++++++ .../Format-List.Tests.ps1 | 44 ++++++++++++++ .../Format-Table.Tests.ps1 | 44 ++++++++++++++ .../Format-Wide.Tests.ps1 | 52 +++++++++++++++++ 16 files changed, 401 insertions(+), 124 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs index 2179243279d..c2c16ff2cd1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs @@ -41,6 +41,12 @@ public object[] Property private object[] _props; + /// + /// Gets or sets the properties to exclude from formatting. + /// + [Parameter] + public string[] ExcludeProperty { get; set; } + /// /// /// @@ -61,6 +67,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() { FormattingCommandLineParameters parameters = new(); + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if ((_props is not null && _props.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (_props != null) { ParameterProcessor processor = new(new FormatObjectParameterDefinition()); @@ -68,15 +86,17 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() parameters.mshParameterList = processor.ProcessParameters(_props, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (_props is null || _props.Length == 0) { - ReportCannotSpecifyViewAndProperty(); + ParameterProcessor processor = new(new FormatObjectParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - - parameters.viewName = this.View; } parameters.groupByParameter = this.ProcessGroupByParameter(); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs index aeddf498f44..c6aef5c20be 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs @@ -42,9 +42,14 @@ public object Property private object _prop; /// - /// Optional, non positional parameter. + /// Gets or sets the properties to exclude from formatting. + /// + [Parameter] + public string[] ExcludeProperty { get; set; } + + /// + /// Gets or sets a value indicating whether to autosize the output. /// - /// [Parameter] public SwitchParameter AutoSize { @@ -74,6 +79,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() { FormattingCommandLineParameters parameters = new(); + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if (_prop is not null || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (_prop != null) { ParameterProcessor processor = new(new FormatWideParameterDefinition()); @@ -81,15 +98,17 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() parameters.mshParameterList = processor.ProcessParameters(new object[] { _prop }, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (_prop is null) { - ReportCannotSpecifyViewAndProperty(); + ParameterProcessor processor = new(new FormatWideParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - - parameters.viewName = this.View; } // we cannot specify -column and -autosize, they are mutually exclusive diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs index 9db27335da5..e0b0bff07c5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs @@ -12,47 +12,6 @@ namespace Microsoft.PowerShell.Commands { - /// - /// Helper class to do wildcard matching on PSPropertyExpressions. - /// - internal sealed class PSPropertyExpressionFilter - { - /// - /// Initializes a new instance of the class - /// with the specified array of patterns. - /// - /// Array of pattern strings to use. - internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) - { - ArgumentNullException.ThrowIfNull(wildcardPatternsStrings); - - _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; - for (int k = 0; k < wildcardPatternsStrings.Length; k++) - { - _wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase); - } - } - - /// - /// Try to match the expression against the array of wildcard patterns. - /// The first match shortcircuits the search. - /// - /// PSPropertyExpression to test against. - /// True if there is a match, else false. - internal bool IsMatch(PSPropertyExpression expression) - { - for (int k = 0; k < _wildcardPatterns.Length; k++) - { - if (_wildcardPatterns[k].IsMatch(expression.ToString())) - return true; - } - - return false; - } - - private readonly WildcardPattern[] _wildcardPatterns; - } - internal sealed class SelectObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs index 18b74cbcfe1..683553a3b30 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs @@ -9,6 +9,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -729,6 +730,12 @@ public class OuterFormatTableAndListBase : OuterFormatShapeCommandBase [Parameter(Position = 0)] public object[] Property { get; set; } + /// + /// Optional parameter for excluding properties from formatting. + /// + [Parameter] + public string[] ExcludeProperty { get; set; } + #endregion internal override FormattingCommandLineParameters GetCommandLineParameters() @@ -751,6 +758,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() internal void GetCommandLineProperties(FormattingCommandLineParameters parameters, bool isTable) { + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if ((Property is not null && Property.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (Property != null) { CommandParameterDefinition def; @@ -765,15 +784,21 @@ internal void GetCommandLineProperties(FormattingCommandLineParameters parameter parameters.mshParameterList = processor.ProcessParameters(Property, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (Property is null || Property.Length == 0) { - ReportCannotSpecifyViewAndProperty(); - } + CommandParameterDefinition def = isTable + ? new FormatTableParameterDefinition() + : new FormatListParameterDefinition(); + ParameterProcessor processor = new ParameterProcessor(def); + TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); - parameters.viewName = this.View; + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); + } } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs index 7f88727ace7..498f6098a24 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs @@ -70,6 +70,11 @@ internal sealed class FormattingCommandLineParameters /// Extension mechanism for shape specific parameters. /// internal ShapeSpecificParameters shapeParameters = null; + + /// + /// Filter for excluding properties from formatting. + /// + internal PSPropertyExpressionFilter excludePropertyFilter = null; } /// diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs index db1bc0704aa..cb20e708cc4 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Linq; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Internal; @@ -348,7 +349,19 @@ protected class DataBaseInfo protected DataBaseInfo dataBaseInfo = new DataBaseInfo(); protected List activeAssociationList = null; - protected FormattingCommandLineParameters inputParameters = null; + /// + /// Apply ExcludeProperty filter to activeAssociationList if specified. + /// This method filters and updates "activeAssociationList" instance property. + /// + protected void ApplyExcludePropertyFilter() + { + if (this.parameters is not null && this.parameters.excludePropertyFilter is not null) + { + this.activeAssociationList = this.activeAssociationList + .Where(item => !this.parameters.excludePropertyFilter.IsMatch(item.ResolvedExpression)) + .ToList(); + } + } protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex, FieldFormattingDirective directive) diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs index b2bbd27c2b9..972a7160830 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Internal; @@ -16,7 +17,6 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; } internal override FormatStartData GenerateStartData(PSObject so) @@ -40,7 +40,7 @@ internal override FormatEntryData GeneratePayload(PSObject so, int enumerationLi private ComplexViewEntry GenerateComplexViewEntryFromProperties(PSObject so, int enumerationLimit) { ComplexViewObjectBrowser browser = new ComplexViewObjectBrowser(this.ErrorManager, this.expressionFactory, enumerationLimit); - return browser.GenerateView(so, this.inputParameters); + return browser.GenerateView(so, this.parameters); } private ComplexViewEntry GenerateComplexViewEntryFromDataBaseInfo(PSObject so, int enumerationLimit) @@ -425,17 +425,18 @@ internal ComplexViewObjectBrowser(FormatErrorManager resultErrorManager, PSPrope /// of the object. /// /// Object to process. - /// Parameters from the command line. + /// Parameters from the command line. /// Complex view entry to send to the output command. - internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters inputParameters) + internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters parameters) { - _complexSpecificParameters = (ComplexSpecificParameters)inputParameters.shapeParameters; + _parameters = parameters; + _complexSpecificParameters = (ComplexSpecificParameters)parameters.shapeParameters; int maxDepth = _complexSpecificParameters.maxDepth; TraversalInfo level = new TraversalInfo(0, maxDepth); List mshParameterList = null; - mshParameterList = inputParameters.mshParameterList; + mshParameterList = parameters.mshParameterList; // create a top level entry as root of the tree ComplexViewEntry cve = new ComplexViewEntry(); @@ -513,6 +514,14 @@ private void DisplayObject(PSObject so, TraversalInfo currentLevel, List activeAssociationList = AssociationManager.SetupActiveProperties(parameterList, so, _expressionFactory); + // Apply ExcludeProperty filter if specified + if (_parameters != null && _parameters.excludePropertyFilter != null) + { + activeAssociationList = activeAssociationList + .Where(item => !_parameters.excludePropertyFilter.IsMatch(item.ResolvedExpression)) + .ToList(); + } + // create a format entry FormatEntry fe = new FormatEntry(); formatValueList.Add(fe); @@ -758,6 +767,7 @@ private List AddIndentationLevel(List formatValueList) return feFrame.formatValueList; } + private FormattingCommandLineParameters _parameters; private ComplexSpecificParameters _complexSpecificParameters; /// diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs index f7acd9ed226..6ef1b24ce14 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs @@ -31,7 +31,7 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper _listBody = (ListControlBody)this.dataBaseInfo.view.mainControl; } - this.inputParameters = parameters; + this.parameters = parameters; SetUpActiveProperties(so); } @@ -227,10 +227,12 @@ private void SetUpActiveProperties(PSObject so) { List mshParameterList = null; - if (this.inputParameters != null) - mshParameterList = this.inputParameters.mshParameterList; + if (this.parameters != null) + mshParameterList = this.parameters.mshParameterList; this.activeAssociationList = AssociationManager.SetupActiveProperties(mshParameterList, so, this.expressionFactory); + + ApplyExcludePropertyFilter(); } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs index 0ddc307b646..33d2bd53506 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs @@ -40,40 +40,45 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper rawMshParameterList = parameters.mshParameterList; // check if we received properties from the command line - if (rawMshParameterList != null && rawMshParameterList.Count > 0) + if (rawMshParameterList is not null && rawMshParameterList.Count > 0) { this.activeAssociationList = AssociationManager.ExpandTableParameters(rawMshParameterList, so); - return; } - - // we did not get any properties: - // try to get properties from the default property set of the object - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) + else { - // we got a valid set of properties from the default property set..add computername for - // remoteobjects (if available) - if (PSObjectHelper.ShouldShowComputerNameProperty(so)) + // we did not get any properties: + // try to get properties from the default property set of the object + this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (this.activeAssociationList.Count > 0) { - activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, - new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); + // we got a valid set of properties from the default property set..add computername for + // remoteobjects (if available) + if (PSObjectHelper.ShouldShowComputerNameProperty(so)) + { + activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, + new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); + } + } + else + { + // we failed to get anything from the default property set + this.activeAssociationList = AssociationManager.ExpandAll(so); + if (this.activeAssociationList.Count > 0) + { + // Remove PSComputerName and PSShowComputerName from the display as needed. + AssociationManager.HandleComputerNameProperties(so, activeAssociationList); + FilterActiveAssociationList(); + } + else + { + // we were unable to retrieve any properties, so we leave an empty list + this.activeAssociationList = new List(); + return; + } } - - return; - } - - // we failed to get anything from the default property set - this.activeAssociationList = AssociationManager.ExpandAll(so); - if (this.activeAssociationList.Count > 0) - { - // Remove PSComputerName and PSShowComputerName from the display as needed. - AssociationManager.HandleComputerNameProperties(so, activeAssociationList); - FilterActiveAssociationList(); - return; } - // we were unable to retrieve any properties, so we leave an empty list - this.activeAssociationList = new List(); + ApplyExcludePropertyFilter(); } /// diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs index 26c0af670c7..a45c006e7fc 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs @@ -13,7 +13,6 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; } internal override FormatStartData GenerateStartData(PSObject so) @@ -159,39 +158,37 @@ private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enume private void SetUpActiveProperty(PSObject so) { - List rawMshParameterList = null; - - if (this.inputParameters != null) - rawMshParameterList = this.inputParameters.mshParameterList; + List rawMshParameterList = this.parameters?.mshParameterList; // check if we received properties from the command line - if (rawMshParameterList != null && rawMshParameterList.Count > 0) + if (rawMshParameterList is not null && rawMshParameterList.Count > 0) { this.activeAssociationList = AssociationManager.ExpandParameters(rawMshParameterList, so); - return; - } - - // we did not get any properties: - // try to get the display property of the object - PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); - if (displayNameExpression != null) - { - this.activeAssociationList = new List(); - this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression)); - return; } - - // try to get the default property set (we will use the first property) - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) + else { - // we got a valid set of properties from the default property set - return; + // we did not get any properties: + // try to get the display property of the object + PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); + if (displayNameExpression is not null) + { + this.activeAssociationList = new List(); + this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression)); + } + else + { + // try to get the default property set (we will use the first property) + this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (this.activeAssociationList.Count == 0) + { + // we failed to get anything from the default property set + // just get all the properties + this.activeAssociationList = AssociationManager.ExpandAll(so); + } + } } - // we failed to get anything from the default property set - // just get all the properties - this.activeAssociationList = AssociationManager.ExpandAll(so); + ApplyExcludePropertyFilter(); } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs index 552d511fd4e..55ee3c30ddb 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs @@ -375,5 +375,42 @@ private static PSObject IfHashtableWrapAsPSCustomObject(PSObject target, out boo private bool _isResolved = false; #endregion Private Members + + } + + /// + /// Helper class to do wildcard matching on PSPropertyExpressions. + /// + internal sealed class PSPropertyExpressionFilter + { + /// + /// Initializes a new instance of the class + /// with the specified array of patterns. + /// + /// Array of pattern strings to use. + internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) + { + ArgumentNullException.ThrowIfNull(wildcardPatternsStrings); + + _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; + for (int k = 0; k < wildcardPatternsStrings.Length; k++) + { + _wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase); + } + } + + /// + /// Try to match the expression against the array of wildcard patterns. + /// The first match short-circuits the search. + /// + /// PSPropertyExpression to test against. + /// True if there is a match, else false. + internal bool IsMatch(PSPropertyExpression expression) + { + string expressionString = expression.ToString(); + return _wildcardPatterns.Any(pattern => pattern.IsMatch(expressionString)); + } + + private readonly WildcardPattern[] _wildcardPatterns; } } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 335a704dd62..73406e449bf 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -2504,7 +2504,8 @@ private static void NativeCommandArgumentCompletion( case "Format-Table": case "Format-Wide": { - if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) + if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase) + || parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase)) { NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]); } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 index 80aad6ef5b9..c82d8b77b8a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 @@ -466,4 +466,48 @@ SelectScriptBlock $ps.Invoke() -replace '\r?\n', "^" | Should -BeExactly $expectedOutput $ps.Streams.Error | Should -BeNullOrEmpty } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified properties' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + $result = $obj | Format-Custom -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Not -Match 'Age' + } + + It 'Should work with wildcard patterns' { + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + $result = $obj | Format-Custom -ExcludeProperty Prop* | Out-String + $result | Should -Match 'Other' + $result | Should -Not -Match 'Prop1' + $result | Should -Not -Match 'Prop2' + } + + It 'Should work without Property parameter (implies -Property *)' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + $result = $obj | Format-Custom -ExcludeProperty B | Out-String + $result | Should -Match 'A\s*=' + $result | Should -Match 'C\s*=' + $result | Should -Not -Match 'B\s*=' + } + + It 'Should work with Property parameter' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + $result = $obj | Format-Custom -Property Name, Age, City, Country -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Match 'Country' + $result | Should -Not -Match 'Age' + } + + It 'Should handle multiple excluded properties' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + $result = $obj | Format-Custom -ExcludeProperty B, D | Out-String + $result | Should -Match 'A\s*=' + $result | Should -Match 'C\s*=' + $result | Should -Not -Match 'B\s*=' + $result | Should -Not -Match 'D\s*=' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 index b9079b92139..342383f3880 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 @@ -261,4 +261,48 @@ Describe 'Format-List color tests' -Tag 'CI' { $output = Get-Content "$TestDrive/outfile.txt" -Raw $output.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified properties' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + $result = $obj | Format-List -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Not -Match 'Age' + } + + It 'Should work with wildcard patterns' { + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + $result = $obj | Format-List -ExcludeProperty Prop* | Out-String + $result | Should -Match 'Other' + $result | Should -Not -Match 'Prop1' + $result | Should -Not -Match 'Prop2' + } + + It 'Should work without Property parameter (implies -Property *)' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + $result = $obj | Format-List -ExcludeProperty B | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + } + + It 'Should work with Property parameter' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + $result = $obj | Format-List -Property Name, Age, City, Country -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Match 'Country' + $result | Should -Not -Match 'Age' + } + + It 'Should handle multiple excluded properties' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + $result = $obj | Format-List -ExcludeProperty B, D | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + $result | Should -Not -Match 'D' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 index 34f2245909c..d102ef8bbdf 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 @@ -953,4 +953,48 @@ Describe 'Table color tests' -Tag 'CI' { $actual | Should -BeExactly $expected } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified properties' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + $result = $obj | Format-Table -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Not -Match 'Age' + } + + It 'Should work with wildcard patterns' { + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + $result = $obj | Format-Table -ExcludeProperty Prop* | Out-String + $result | Should -Match 'Other' + $result | Should -Not -Match 'Prop1' + $result | Should -Not -Match 'Prop2' + } + + It 'Should work without Property parameter (implies -Property *)' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + $result = $obj | Format-Table -ExcludeProperty B | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + } + + It 'Should work with Property parameter' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + $result = $obj | Format-Table -Property Name, Age, City, Country -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Match 'Country' + $result | Should -Not -Match 'Age' + } + + It 'Should handle multiple excluded properties' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + $result = $obj | Format-Table -ExcludeProperty B, D | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + $result | Should -Not -Match 'D' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 index c81db9fa1f9..10acd699068 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 @@ -105,4 +105,56 @@ Describe "Format-Wide DRT basic functionality" -Tags "CI" { $result | Should -Match " GroupingKey:" $result | Should -Match "name2\s+name3" } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified property and display first remaining' { + # PSCustomObject properties are in definition order: Name, Age, City + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + # Exclude Name, should display Age (first remaining) + $result = $obj | Format-Wide -ExcludeProperty Name | Out-String + $result | Should -Match '30' + $result | Should -Not -Match 'Test' + } + + It 'Should work with wildcard patterns' { + # Properties: Prop1, Prop2, Other + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + # Exclude Prop*, should display Other (only remaining) + $result = $obj | Format-Wide -ExcludeProperty Prop* | Out-String + $result | Should -Match '3' + $result | Should -Not -Match '1' + $result | Should -Not -Match '2' + } + + It 'Should work without Property parameter (implies -Property *)' { + # Properties: A, B, C + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + # Exclude B, C - should display A (first remaining) + $result = $obj | Format-Wide -ExcludeProperty B, C | Out-String + $result | Should -Match '1' + $result | Should -Not -Match '2' + $result | Should -Not -Match '3' + } + + It 'Should display first remaining property after exclusion' { + # Properties: Name, Age, City, Country + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + # Exclude Name and Age, should display City (first remaining) + $result = $obj | Format-Wide -ExcludeProperty Name, Age | Out-String + $result | Should -Match 'Seattle' + $result | Should -Not -Match 'Test' + $result | Should -Not -Match '30' + # USA might appear but not in the wide field, or might not appear at all since only first property is shown + } + + It 'Should handle multiple excluded properties' { + # Properties: A, B, C, D + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + # Exclude A and B, should display C (first remaining) + $result = $obj | Format-Wide -ExcludeProperty A, B | Out-String + $result | Should -Match '3' + $result | Should -Not -Match '1' + $result | Should -Not -Match '2' + } + } } From 2a49561af4c92b90d5ada6f7780a8a3dbdb02d32 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:14:07 -0800 Subject: [PATCH 137/255] Update `Microsoft.PowerShell.PSResourceGet` to `v1.2.0-preview5` (#26589) --- src/Modules/PSGalleryModules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index d2a49e26d2a..b6e5506e0b0 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -13,7 +13,7 @@ - + From 235ff058e71d6caca57d1f06ed49de5fe4cdb223 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Thu, 11 Dec 2025 01:22:36 +0900 Subject: [PATCH 138/255] Centralize ExcludeProperty filter application in ViewGenerator base class (#26574) --- .../common/FormatViewGenerator.cs | 46 +++++-- .../common/FormatViewGenerator_Complex.cs | 10 +- .../common/FormatViewGenerator_List.cs | 34 ++--- .../common/FormatViewGenerator_Table.cs | 116 +++++++++--------- .../common/FormatViewGenerator_Wide.cs | 82 ++++++------- 5 files changed, 146 insertions(+), 142 deletions(-) diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs index cb20e708cc4..963b5a0f88b 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -348,19 +348,49 @@ protected class DataBaseInfo protected DataBaseInfo dataBaseInfo = new DataBaseInfo(); - protected List activeAssociationList = null; /// - /// Apply ExcludeProperty filter to activeAssociationList if specified. - /// This method filters and updates "activeAssociationList" instance property. + /// Builds the raw association list for the given object. + /// Subclasses override this to provide cmdlet-specific property expansion logic. /// - protected void ApplyExcludePropertyFilter() + /// The object to build the association list for. + /// The list of properties specified by the user, or null if not specified. + /// The raw association list, or null if not applicable. + protected virtual List BuildRawAssociationList(PSObject so, List propertyList) { - if (this.parameters is not null && this.parameters.excludePropertyFilter is not null) + return null; + } + + /// + /// Builds the active association list for the given object, with ExcludeProperty filter applied. + /// + /// The object to build the association list for. + /// The filtered association list. + protected List BuildActiveAssociationList(PSObject so) + { + var propertyList = parameters?.mshParameterList; + var excludeFilter = parameters?.excludePropertyFilter; + var rawList = BuildRawAssociationList(so, propertyList); + return ApplyExcludeFilter(rawList, excludeFilter); + } + + /// + /// Applies the ExcludeProperty filter to the given association list. + /// + /// The list to filter. + /// The exclude filter to apply. + /// The filtered list, or the original list if no filter is specified. + internal static List ApplyExcludeFilter( + List associationList, + PSPropertyExpressionFilter excludeFilter) + { + if (associationList is null || excludeFilter is null) { - this.activeAssociationList = this.activeAssociationList - .Where(item => !this.parameters.excludePropertyFilter.IsMatch(item.ResolvedExpression)) - .ToList(); + return associationList; } + + return associationList + .Where(item => !excludeFilter.IsMatch(item.ResolvedExpression)) + .ToList(); } protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex, diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs index 972a7160830..b81c0c0f860 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Internal; @@ -514,13 +513,8 @@ private void DisplayObject(PSObject so, TraversalInfo currentLevel, List activeAssociationList = AssociationManager.SetupActiveProperties(parameterList, so, _expressionFactory); - // Apply ExcludeProperty filter if specified - if (_parameters != null && _parameters.excludePropertyFilter != null) - { - activeAssociationList = activeAssociationList - .Where(item => !_parameters.excludePropertyFilter.IsMatch(item.ResolvedExpression)) - .ToList(); - } + // Apply ExcludeProperty filter using the centralized method + activeAssociationList = ViewGenerator.ApplyExcludeFilter(activeAssociationList, _parameters?.excludePropertyFilter); // create a format entry FormatEntry fe = new FormatEntry(); diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs index 6ef1b24ce14..35287d7c2e4 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs @@ -30,9 +30,14 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper { _listBody = (ListControlBody)this.dataBaseInfo.view.mainControl; } + } - this.parameters = parameters; - SetUpActiveProperties(so); + /// + /// Builds the raw association list for list formatting. + /// + protected override List BuildRawAssociationList(PSObject so, List propertyList) + { + return AssociationManager.SetupActiveProperties(propertyList, so, this.expressionFactory); } /// @@ -178,17 +183,14 @@ private ListControlEntryDefinition GetActiveListControlEntryDefinition(ListContr private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enumerationLimit) { - // compute active properties every time - if (this.activeAssociationList == null) - { - SetUpActiveProperties(so); - } + // Build active association list (with ExcludeProperty filter applied) + var associationList = BuildActiveAssociationList(so); ListViewEntry lve = new ListViewEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < associationList.Count; k++) { - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k]; + MshResolvedExpressionParameterAssociation a = associationList[k]; ListViewField lvf = new ListViewField(); if (a.OriginatingParameter != null) @@ -218,21 +220,7 @@ private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enume lvf.formatPropertyField.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, a.ResolvedExpression, directive); lve.listViewFieldList.Add(lvf); } - - this.activeAssociationList = null; return lve; } - - private void SetUpActiveProperties(PSObject so) - { - List mshParameterList = null; - - if (this.parameters != null) - mshParameterList = this.parameters.mshParameterList; - - this.activeAssociationList = AssociationManager.SetupActiveProperties(mshParameterList, so, this.expressionFactory); - - ApplyExcludePropertyFilter(); - } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs index 33d2bd53506..3b14c0754ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs @@ -14,6 +14,8 @@ internal sealed class TableViewGenerator : ViewGenerator // tableBody to use for this instance of the ViewGenerator; private TableControlBody _tableBody; + private List _activeAssociationList; + internal override void Initialize(TerminatingErrorContext terminatingErrorContext, PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) { base.Initialize(terminatingErrorContext, mshExpressionFactory, db, view, formatParameters); @@ -34,51 +36,48 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper _tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl; } - List rawMshParameterList = null; - - if (parameters != null) - rawMshParameterList = parameters.mshParameterList; + // Build the active association list (with ExcludeProperty filter applied) + _activeAssociationList = BuildActiveAssociationList(so); + } + /// + /// Builds the raw association list for table formatting. + /// + protected override List BuildRawAssociationList(PSObject so, List propertyList) + { // check if we received properties from the command line - if (rawMshParameterList is not null && rawMshParameterList.Count > 0) + if (propertyList is not null && propertyList.Count > 0) { - this.activeAssociationList = AssociationManager.ExpandTableParameters(rawMshParameterList, so); + return AssociationManager.ExpandTableParameters(propertyList, so); } - else + + // we did not get any properties: + // try to get properties from the default property set of the object + var list = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (list.Count > 0) { - // we did not get any properties: - // try to get properties from the default property set of the object - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) - { - // we got a valid set of properties from the default property set..add computername for - // remoteobjects (if available) - if (PSObjectHelper.ShouldShowComputerNameProperty(so)) - { - activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, - new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); - } - } - else + // we got a valid set of properties from the default property set..add computername for + // remoteobjects (if available) + if (PSObjectHelper.ShouldShowComputerNameProperty(so)) { - // we failed to get anything from the default property set - this.activeAssociationList = AssociationManager.ExpandAll(so); - if (this.activeAssociationList.Count > 0) - { - // Remove PSComputerName and PSShowComputerName from the display as needed. - AssociationManager.HandleComputerNameProperties(so, activeAssociationList); - FilterActiveAssociationList(); - } - else - { - // we were unable to retrieve any properties, so we leave an empty list - this.activeAssociationList = new List(); - return; - } + list.Add(new MshResolvedExpressionParameterAssociation(null, + new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); } + + return list; } - ApplyExcludePropertyFilter(); + // we failed to get anything from the default property set + list = AssociationManager.ExpandAll(so); + if (list.Count > 0) + { + // Remove PSComputerName and PSShowComputerName from the display as needed. + AssociationManager.HandleComputerNameProperties(so, list); + return LimitAssociationListSize(list); + } + + // we were unable to retrieve any properties, so we leave an empty list + return new List(); } /// @@ -129,30 +128,29 @@ internal override FormatStartData GenerateStartData(PSObject so) } /// - /// Method to filter resolved expressions as per table view needs. + /// Limits the association list size for table view. /// For v1.0, table view supports only 10 properties. - /// - /// This method filters and updates "activeAssociationList" instance property. /// - /// None. - /// This method updates "activeAssociationList" instance property. - private void FilterActiveAssociationList() + /// The list to limit. + /// The limited list. + private static List LimitAssociationListSize( + List list) { - // we got a valid set of properties from the default property set - // make sure we do not have too many properties - // NOTE: this is an arbitrary number, chosen to be a sensitive default - const int nMax = 10; + const int maxCount = 10; + + if (list.Count <= maxCount) + { + return list; + } - if (activeAssociationList.Count > nMax) + var result = new List(maxCount); + for (int k = 0; k < maxCount; k++) { - List tmp = this.activeAssociationList; - this.activeAssociationList = new List(); - for (int k = 0; k < nMax; k++) - this.activeAssociationList.Add(tmp[k]); + result.Add(list[k]); } - return; + return result; } private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) @@ -228,9 +226,9 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) thi.hideHeader = this.HideHeaders; thi.repeatHeader = this.RepeatHeader; - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < _activeAssociationList.Count; k++) { - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k]; + MshResolvedExpressionParameterAssociation a = _activeAssociationList[k]; TableColumnInfo ci = new TableColumnInfo(); // set the label of the column @@ -241,7 +239,7 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) ci.propertyName = (string)key; } - ci.propertyName ??= this.activeAssociationList[k].ResolvedExpression.ToString(); + ci.propertyName ??= _activeAssociationList[k].ResolvedExpression.ToString(); // set the width of the table if (a.OriginatingParameter != null) @@ -473,13 +471,13 @@ private TableRowEntry GenerateTableRowEntryFromDataBaseInfo(PSObject so, int enu private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int enumerationLimit) { TableRowEntry tre = new TableRowEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < _activeAssociationList.Count; k++) { FormatPropertyField fpf = new FormatPropertyField(); FieldFormattingDirective directive = null; - if (activeAssociationList[k].OriginatingParameter != null) + if (_activeAssociationList[k].OriginatingParameter != null) { - directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; + directive = _activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; } if (directive is null) @@ -488,7 +486,7 @@ private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int e directive.isTable = true; } - fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive); + fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, _activeAssociationList[k].ResolvedExpression, directive); tre.formatPropertyFieldList.Add(fpf); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs index a45c006e7fc..bfa364cc450 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs @@ -15,6 +15,40 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper base.Initialize(errorContext, expressionFactory, so, db, parameters); } + /// + /// Builds the raw association list for wide formatting. + /// + protected override List BuildRawAssociationList(PSObject so, List propertyList) + { + // check if we received properties from the command line + if (propertyList is not null && propertyList.Count > 0) + { + return AssociationManager.ExpandParameters(propertyList, so); + } + + // we did not get any properties: + // try to get the display property of the object + PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); + if (displayNameExpression is not null) + { + return new List + { + new MshResolvedExpressionParameterAssociation(null, displayNameExpression) + }; + } + + // try to get the default property set (we will use the first property) + var list = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (list.Count == 0) + { + // we failed to get anything from the default property set + // just get all the properties + list = AssociationManager.ExpandAll(so); + } + + return list; + } + internal override FormatStartData GenerateStartData(PSObject so) { FormatStartData startFormat = base.GenerateStartData(so); @@ -129,20 +163,17 @@ private WideControlEntryDefinition GetActiveWideControlEntryDefinition(WideContr private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enumerationLimit) { - // compute active properties every time - if (this.activeAssociationList == null) - { - SetUpActiveProperty(so); - } + // Build active association list (with ExcludeProperty filter applied) + var associationList = BuildActiveAssociationList(so); WideViewEntry wve = new WideViewEntry(); FormatPropertyField fpf = new FormatPropertyField(); wve.formatPropertyField = fpf; - if (this.activeAssociationList.Count > 0) + if (associationList.Count > 0) { // get the first one - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[0]; + MshResolvedExpressionParameterAssociation a = associationList[0]; FieldFormattingDirective directive = null; if (a.OriginatingParameter != null) { @@ -151,44 +182,7 @@ private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enume fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, a.ResolvedExpression, directive); } - - this.activeAssociationList = null; return wve; } - - private void SetUpActiveProperty(PSObject so) - { - List rawMshParameterList = this.parameters?.mshParameterList; - - // check if we received properties from the command line - if (rawMshParameterList is not null && rawMshParameterList.Count > 0) - { - this.activeAssociationList = AssociationManager.ExpandParameters(rawMshParameterList, so); - } - else - { - // we did not get any properties: - // try to get the display property of the object - PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); - if (displayNameExpression is not null) - { - this.activeAssociationList = new List(); - this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression)); - } - else - { - // try to get the default property set (we will use the first property) - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count == 0) - { - // we failed to get anything from the default property set - // just get all the properties - this.activeAssociationList = AssociationManager.ExpandAll(so); - } - } - } - - ApplyExcludePropertyFilter(); - } } } From 7f92d588fa02d58e31ba7c86840f3f1c94bdc466 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Thu, 11 Dec 2025 03:21:32 +0900 Subject: [PATCH 139/255] Add ToRegex method to WildcardPattern class (#26515) --- .../engine/regex.cs | 41 +++++++--- .../engine/WildcardPattern.Tests.ps1 | 77 +++++++++++++++++++ 2 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 test/powershell/engine/WildcardPattern.Tests.ps1 diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index d69f3bdb88f..6e0ef9741d6 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -73,18 +73,6 @@ public sealed class WildcardPattern // Default is WildcardOptions.None. internal WildcardOptions Options { get; } - /// - /// Wildcard pattern converted to regex pattern. - /// - internal string PatternConvertedToRegex - { - get - { - var patternRegex = WildcardPatternToRegexParser.Parse(this); - return patternRegex.ToString(); - } - } - /// /// Initializes and instance of the WildcardPattern class /// for the specified wildcard pattern. @@ -205,6 +193,35 @@ public bool IsMatch(string input) return input != null && _isMatch(input); } + /// + /// Converts the wildcard pattern to its regular expression equivalent. + /// + /// + /// A object that represents the regular expression equivalent of the wildcard pattern. + /// The regex is configured with options matching the wildcard pattern's options. + /// + /// + /// This method converts a wildcard pattern to a regular expression. + /// The conversion follows these rules: + /// + /// * (asterisk) converts to .* (matches any string) + /// ? (question mark) converts to . (matches any single character) + /// [abc] (bracket expression) converts to [abc] (matches any character in the set) + /// Literal characters are escaped as needed for regex + /// + /// + /// + /// + /// var pattern = new WildcardPattern("*.txt"); + /// Regex regex = pattern.ToRegex(); + /// // regex.ToString() returns: "\.txt$" + /// + /// + public Regex ToRegex() + { + return WildcardPatternToRegexParser.Parse(this); + } + /// /// Escape special chars, except for those specified in , in a string by replacing them with their escape codes. /// diff --git a/test/powershell/engine/WildcardPattern.Tests.ps1 b/test/powershell/engine/WildcardPattern.Tests.ps1 new file mode 100644 index 00000000000..81ced10f253 --- /dev/null +++ b/test/powershell/engine/WildcardPattern.Tests.ps1 @@ -0,0 +1,77 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "WildcardPattern.ToRegex Tests" -Tags "CI" { + It "Converts '' to regex pattern ''" -TestCases @( + @{ Pattern = '*.txt'; Expected = '\.txt$' } + @{ Pattern = 'test?.log'; Expected = '^test.\.log$' } + @{ Pattern = 'file[0-9].txt'; Expected = '^file[0-9]\.txt$' } + @{ Pattern = 'test.log'; Expected = '^test\.log$' } + @{ Pattern = '*test*file*.txt'; Expected = 'test.*file.*\.txt$' } + @{ Pattern = 'file[0-9][a-z].txt'; Expected = '^file[0-9][a-z]\.txt$' } + @{ Pattern = 'test*'; Expected = '^test' } + @{ Pattern = '*test*'; Expected = 'test' } + ) { + param($Pattern, $Expected) + $wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern) + $regex = $wildcardPattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly $Expected + } + + It "Converts '' with option" -TestCases @( + @{ Pattern = 'TEST'; OptionName = 'IgnoreCase'; Option = [System.Management.Automation.WildcardOptions]::IgnoreCase; Expected = '^TEST$'; ExpectedRegexOptions = 'IgnoreCase, Singleline'; TestString = 'test'; ExpectedMatch = $true } + @{ Pattern = 'test'; OptionName = 'CultureInvariant'; Option = [System.Management.Automation.WildcardOptions]::CultureInvariant; Expected = '^test$'; ExpectedRegexOptions = 'Singleline, CultureInvariant'; TestString = 'test'; ExpectedMatch = $true } + @{ Pattern = 'test*'; OptionName = 'Compiled'; Option = [System.Management.Automation.WildcardOptions]::Compiled; Expected = '^test'; ExpectedRegexOptions = 'Compiled, Singleline'; TestString = 'testing'; ExpectedMatch = $true } + ) { + param($Pattern, $OptionName, $Option, $Expected, $ExpectedRegexOptions, $TestString, $ExpectedMatch) + $wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern, $Option) + $regex = $wildcardPattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly $Expected + $regex.Options.ToString() | Should -BeExactly $ExpectedRegexOptions + $regex.IsMatch($TestString) | Should -Be $ExpectedMatch + } + + It "Regex from '' matches '': " -TestCases @( + @{ Pattern = '*test*file*.txt'; TestString = 'mytestmyfile123.txt'; ShouldMatch = $true } + @{ Pattern = 'file[0-9][a-z].txt'; TestString = 'file5a.txt'; ShouldMatch = $true } + @{ Pattern = 'file[0-9][a-z].txt'; TestString = 'file55.txt'; ShouldMatch = $false } + ) { + param($Pattern, $TestString, $ShouldMatch) + $regex = [System.Management.Automation.WildcardPattern]::new($Pattern).ToRegex() + $regex.IsMatch($TestString) | Should -Be $ShouldMatch + } + + Context "Edge cases" { + It "Handles empty pattern" { + $pattern = [System.Management.Automation.WildcardPattern]::new("") + $regex = $pattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -Be "^$" + } + + It "Handles pattern with only asterisk" { + $pattern = [System.Management.Automation.WildcardPattern]::new("*") + $regex = $pattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly "" + $regex.IsMatch("anything") | Should -BeTrue + $regex.IsMatch("") | Should -BeTrue + } + + It "Handles escaped '' wildcard character" -TestCases @( + @{ Char = '*'; Pattern = 'file`*.txt'; Expected = '^file\*\.txt$' } + @{ Char = '?'; Pattern = 'file`?.txt'; Expected = '^file\?\.txt$' } + @{ Char = '['; Pattern = 'file`[.txt'; Expected = '^file\[\.txt$' } + @{ Char = ']'; Pattern = 'file`].txt'; Expected = '^file]\.txt$' } + ) { + param($Char, $Pattern, $Expected) + $wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern) + $regex = $wildcardPattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly $Expected + } + + } +} From 06ffbaeb443b0420052f31681de076216d73a84f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:58:06 -0800 Subject: [PATCH 140/255] Bump github/codeql-action from 4.31.6 to 4.31.7 (#26586) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 21bc1ed1e92..55275cbcc3b 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 + uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 + uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3c322397db0..f6ca3ccac1c 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 + uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 with: sarif_file: results.sarif From 2e7765eef17ee274925a97efab1b9f52973eeb59 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:26:46 +0500 Subject: [PATCH 141/255] Fix $PSDefaultParameterValues leak causing tests to skip unexpectedly (#26602) This fixes test isolation issues where six test files were leaking $PSDefaultParameterValues["It:Skip"] settings to subsequent tests, causing widespread test skipping on Linux. The fixes address two distinct patterns: using .Remove() which doesn't restore the original state (4 files), and imbalanced Push-DefaultParameterValueStack/Pop-DefaultParameterValueStack calls (2 files). --- .../Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 | 3 ++- .../Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 | 3 ++- test/powershell/engine/ETS/CimAdapter.Tests.ps1 | 4 +++- test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 | 2 -- test/powershell/engine/Remoting/SessionOption.Tests.ps1 | 3 ++- test/powershell/engine/Security/FileSignature.Tests.ps1 | 1 + 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 index 82f944612f9..f1937f824a6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 @@ -7,6 +7,7 @@ $DefaultResultValue = 0 try { # set up for testing + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = ! $IsWindows Enable-Testhook -testhookName $RenameTesthook # we also set TestStopComputer @@ -73,7 +74,7 @@ try } finally { - $PSDefaultParameterValues.Remove("it:skip") + $global:PSDefaultParameterValues = $originalDefaultParameterValues Disable-Testhook -testhookName $RenameTestHook Disable-Testhook -testhookName TestStopComputer Set-TesthookResult -testhookName $RenameResultName -value 0 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 index 0e50d7c3603..52938a0b881 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 @@ -3,6 +3,7 @@ Describe "Acl cmdlets are available and operate properly" -Tag CI { Context "Windows ACL test" { BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["It:Skip"] = -not $IsWindows } @@ -103,7 +104,7 @@ Describe "Acl cmdlets are available and operate properly" -Tag CI { } AfterAll { - $PSDefaultParameterValues.Remove("It:Skip") + $global:PSDefaultParameterValues = $originalDefaultParameterValues } } } diff --git a/test/powershell/engine/ETS/CimAdapter.Tests.ps1 b/test/powershell/engine/ETS/CimAdapter.Tests.ps1 index 1e71bc28b95..3cfff4bd9e4 100644 --- a/test/powershell/engine/ETS/CimAdapter.Tests.ps1 +++ b/test/powershell/engine/ETS/CimAdapter.Tests.ps1 @@ -3,6 +3,8 @@ Describe "CIM Objects are adapted properly" -Tag @("CI") { BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + function getIndex { param([string[]]$strings,[string]$pattern) @@ -32,7 +34,7 @@ Describe "CIM Objects are adapted properly" -Tag @("CI") { } } AfterAll { - $PSDefaultParameterValues.Remove("it:pending") + $global:PSDefaultParameterValues = $originalDefaultParameterValues } It "Namespace-qualified Win32_Process is present" -Skip:(!$IsWindows) { diff --git a/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 b/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 index a5d49b75005..2093eaa7ce5 100644 --- a/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 +++ b/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 @@ -83,9 +83,7 @@ Describe "JEA session Transcript script test" -Tag @("Feature", 'RequireAdminOnW AfterAll { if ($skipTest) { Pop-DefaultParameterValueStack - return } - Pop-DefaultParameterValueStack } It "Configuration name should be in the transcript header" { diff --git a/test/powershell/engine/Remoting/SessionOption.Tests.ps1 b/test/powershell/engine/Remoting/SessionOption.Tests.ps1 index 1ff51e2a707..e85777e90ba 100644 --- a/test/powershell/engine/Remoting/SessionOption.Tests.ps1 +++ b/test/powershell/engine/Remoting/SessionOption.Tests.ps1 @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. try { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() if ( ! $IsWindows ) { $PSDefaultParameterValues['it:skip'] = $true } @@ -52,5 +53,5 @@ try { } } finally { - $PSDefaultParameterValues.remove("it:skip") + $global:PSDefaultParameterValues = $originalDefaultParameterValues } diff --git a/test/powershell/engine/Security/FileSignature.Tests.ps1 b/test/powershell/engine/Security/FileSignature.Tests.ps1 index 51194903a94..9dd26f98aed 100644 --- a/test/powershell/engine/Security/FileSignature.Tests.ps1 +++ b/test/powershell/engine/Security/FileSignature.Tests.ps1 @@ -89,6 +89,7 @@ Describe "Windows file content signatures" -Tags @('Feature', 'RequireAdminOnWin AfterAll { if ($shouldSkip) { + Pop-DefaultParameterValueStack return } From 592668bd0b0e46e370e9ac1ad4b8f5420efb51ae Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 12 Dec 2025 09:43:08 -0800 Subject: [PATCH 142/255] Refactor the module path construction code to make it more robust and easier to maintain (#26565) --- .../host/msh/ConsoleHost.cs | 21 ++- .../engine/Modules/ModuleIntrinsics.cs | 177 ++++++++---------- test/powershell/Host/ConsoleHost.Tests.ps1 | 27 +++ 3 files changed, 123 insertions(+), 102 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 5f59540ecf4..2d9902d176a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -2201,7 +2201,6 @@ private void ReportException(Exception e, Executor exec) if (e1 != null) { // that didn't work. Write out the error ourselves as a last resort. - ReportExceptionFallback(e, null); } } @@ -2222,7 +2221,7 @@ private void ReportExceptionFallback(Exception e, string header) Console.Error.WriteLine(header); } - if (e == null) + if (e is null) { return; } @@ -2230,7 +2229,9 @@ private void ReportExceptionFallback(Exception e, string header) // See if the exception has an error record attached to it... ErrorRecord er = null; if (e is IContainsErrorRecord icer) + { er = icer.ErrorRecord; + } if (e is PSRemotingTransportException) { @@ -2247,8 +2248,22 @@ private void ReportExceptionFallback(Exception e, string header) } // Add the position message for the error if it's available. - if (er != null && er.InvocationInfo != null) + if (er?.InvocationInfo is { }) + { Console.Error.WriteLine(er.InvocationInfo.PositionMessage); + } + + // Print the stack trace. + Console.Error.WriteLine($"\n--- {e.GetType().FullName} ---"); + Console.Error.WriteLine(e.StackTrace); + + Exception inner = e.InnerException; + while (inner is { }) + { + Console.Error.WriteLine($"--- inner {inner.GetType().FullName} ---"); + Console.Error.WriteLine(inner.StackTrace); + inner = inner.InnerException; + } } /// diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index b687e502763..301ee8c3b49 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -10,9 +10,7 @@ using System.Management.Automation.Language; using System.Text; using System.Threading; - using Microsoft.PowerShell.Commands; -using Microsoft.Win32; using Dbg = System.Management.Automation.Diagnostics; @@ -39,18 +37,22 @@ public class ModuleIntrinsics private static readonly string s_windowsPowerShellPSHomeModulePath = Path.Combine(System.Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "Modules"); + static ModuleIntrinsics() + { + // Initialize the module path. + SetModulePath(); + } + internal ModuleIntrinsics(ExecutionContext context) { _context = context; - - // And initialize the module path... - SetModulePath(); + ModuleTable = new Dictionary(StringComparer.OrdinalIgnoreCase); } private readonly ExecutionContext _context; // Holds the module collection... - internal Dictionary ModuleTable { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal Dictionary ModuleTable { get; } private const int MaxModuleNestingDepth = 10; @@ -176,11 +178,9 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object sb.SessionState = ss; } - else + else if (moduleCode is string sbText) { - var sbText = moduleCode as string; - if (sbText != null) - sb = ScriptBlock.Create(_context, sbText); + sb = ScriptBlock.Create(_context, sbText); } } @@ -1082,91 +1082,80 @@ internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVa return result; } - /// - /// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%) - /// - /// 'Combined path' string to analyze; can not be null. - /// Path to search for; can not be another 'combined path' (semicolon-separated); can not be null. - /// Index of pathToLookFor in pathToScan; -1 if not found. - private static int PathContainsSubstring(string pathToScan, string pathToLookFor) - { - // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(pathToScan != null, "pathToScan should not be null according to contract of the function"); - Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function"); - - int pos = 0; // position of the current substring in pathToScan - string[] substrings = pathToScan.Split(Path.PathSeparator, StringSplitOptions.None); // we want to process empty entries - string goodPathToLookFor = pathToLookFor.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - foreach (string substring in substrings) - { - string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - - // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains') - // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" } - - if (string.Equals(goodSubstring, goodPathToLookFor, StringComparison.OrdinalIgnoreCase)) - { - return pos; // match found - return index of it in the 'pathToScan' string - } - else - { - pos += substring.Length + 1; // '1' is for trailing semicolon - } - } - // if we are here, that means a match was not found - return -1; - } - /// /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there. /// /// Path string (like %Path% or %PSModulePath%). - /// Collection of individual paths to add. + /// An individual path to add, or multiple paths separated by the path separator character. /// -1 to append to the end; 0 to insert in the beginning of the string; etc... /// Result string. - private static string AddToPath(string basePath, string pathToAdd, int insertPosition) + private static string UpdatePath(string basePath, string pathToAdd, ref int insertPosition) { // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function"); - Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + Dbg.Assert(basePath != null, "basePath should not be null according to contract of the function"); + Dbg.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + + // The 'pathToAdd' could be a 'combined path' (path-separator-separated). + string[] newPaths = pathToAdd.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - StringBuilder result = new StringBuilder(basePath); + if (newPaths.Length is 0) + { + // The 'pathToAdd' doesn't really contain any paths to add. + return basePath; + } + + var result = new StringBuilder(basePath, capacity: basePath.Length + pathToAdd.Length + newPaths.Length); + var addedPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + string[] initialPaths = basePath.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (string p in initialPaths) + { + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + addedPaths.Add(Path.TrimEndingDirectorySeparator(p)); + } - if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths + foreach (string subPathToAdd in newPaths) { - foreach (string subPathToAdd in pathToAdd.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + string normalizedPath = Path.TrimEndingDirectorySeparator(subPathToAdd); + if (addedPaths.Contains(normalizedPath)) { - int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly - if (position == -1) // subPathToAdd not found - add it - { - if (insertPosition == -1 || insertPosition > basePath.Length) // append subPathToAdd to the end - { - bool endsWithPathSeparator = false; - if (result.Length > 0) - { - endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); - } + // The normalized sub path was already added - skip it. + continue; + } - if (endsWithPathSeparator) - { - result.Append(subPathToAdd); - } - else - { - result.Append(Path.PathSeparator + subPathToAdd); - } - } - else if (insertPosition > result.Length) - { - // handle case where path is a singleton with no path separator already - result.Append(Path.PathSeparator).Append(subPathToAdd); - } - else // insert at the requested location (this is used by DSC ( location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User)) - { - result.Insert(insertPosition, subPathToAdd + Path.PathSeparator); - } + // The normalized sub path was not found - add it. + if (insertPosition is -1 || insertPosition >= result.Length) + { + // Append the normalized sub path to the end. + if (result.Length > 0 && result[^1] != Path.PathSeparator) + { + result.Append(Path.PathSeparator); } + + result.Append(normalizedPath); + // Next insertion should happen at the end. + insertPosition = result.Length; + } + else + { + // Insert at the requested location. + // This is used by the user-specific module path, the shared module path ( location), and the PSHome module path. + string strToInsert = normalizedPath + Path.PathSeparator; + result.Insert(insertPosition, strToInsert); + + // Next insertion should happen after the just inserted string. + insertPosition += strToInsert.Length; } + + // Add it to the set. + addedPaths.Add(normalizedPath); } return result.ToString(); @@ -1218,7 +1207,7 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location // If the variable isn't set, then set it to the default value - if (currentProcessModulePath == null) // EVT.Process does Not exist - really corner case + if (string.IsNullOrEmpty(currentProcessModulePath)) // EVT.Process does Not exist - really corner case { // Handle the default case... if (string.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to location @@ -1270,21 +1259,6 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM return currentProcessModulePath; } - private static string UpdatePath(string path, string pathToAdd, ref int insertIndex) - { - if (!string.IsNullOrEmpty(pathToAdd)) - { - path = AddToPath(path, pathToAdd, insertIndex); - insertIndex = path.IndexOf(Path.PathSeparator, PathContainsSubstring(path, pathToAdd)); - if (insertIndex != -1) - { - // advance past the path separator - insertIndex++; - } - } - return path; - } - /// /// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these /// strings go through the provider, we need to escape any wildcards before passing them @@ -1358,11 +1332,16 @@ private static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); #if !UNIX - // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete - // otherwise, the user modified it and we should use the process one + // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete. + // Otherwise, the user modified it and we should use the process one. if (string.CompareOrdinal(GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User), currentModulePath) == 0) { - currentModulePath = currentModulePath + Path.PathSeparator + GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + string machineScopeValue = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + currentModulePath = string.IsNullOrEmpty(currentModulePath) + ? machineScopeValue + : string.IsNullOrEmpty(machineScopeValue) + ? currentModulePath + : string.Concat(currentModulePath, Path.PathSeparator, machineScopeValue); } #endif string allUsersModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers); diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index ee501a83b63..534cce2d356 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -386,6 +386,33 @@ export $envVarName='$guid' } } + Context "-SettingsFile Commandline switch set 'PSModulePath'" { + + BeforeAll { + $CustomSettingsFile = Join-Path -Path $TestDrive -ChildPath 'powershell.test.json' + $mPath1 = Join-Path $PSHOME 'Modules' + $mPath2 = Join-Path $TestDrive 'NonExist' + $pathSep = [System.IO.Path]::PathSeparator + + ## Use multiple paths in the setting. + $ModulePath = "${mPath1}${pathSep}${mPath2}".Replace('\', "\\") + Set-Content -Path $CustomSettingsfile -Value "{`"Microsoft.PowerShell:ExecutionPolicy`":`"Unrestricted`", `"PSModulePath`": `"$ModulePath`" }" -ErrorAction Stop + } + + It "Verify PowerShell PSModulePath should contain paths from config file" { + $psModulePath = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command '$env:PSModulePath' + + ## $mPath1 already exists in the value of env PSModulePath, so it won't be added again. + $index = $psModulePath.IndexOf("${mPath1}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) + $index | Should -BeGreaterThan 0 + $index += $mPath1.Length + $psModulePath.IndexOf($mPath1, $index, [System.StringComparison]::OrdinalIgnoreCase) | Should -BeExactly -1 + + ## $mPath2 should be added at the index position 0. + $psModulePath.StartsWith("${mPath2}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) | Should -BeTrue + } + } + Context "Pipe to/from powershell" { BeforeAll { if ($null -ne $PSStyle) { From 15d0a2841379462b159c9203fb4849b03a0f3d4e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 12 Dec 2025 10:40:16 -0800 Subject: [PATCH 143/255] Use consistent indentation in the file `HelpersCommon.psm1` (#26608) --- .../Modules/HelpersCommon/HelpersCommon.psm1 | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 index 5c05198ff8a..02588b612db 100644 --- a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 +++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 @@ -69,7 +69,7 @@ function Get-RandomFileName $SCRIPT:TesthookType = [system.management.automation.internal.internaltesthooks] function Test-TesthookIsSet { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", '')] # , Justification = "an error message is not appropriate for this function")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", '')] # , Justification = "an error message is not appropriate for this function")] param ( [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true)] @@ -197,7 +197,7 @@ public class TestDynamic : DynamicObject # Upload an artifact in VSTS # On other systems will just log where the file was placed function Send-VstsLogFile { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = "needed for VSO")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = "needed for VSO")] param ( [parameter(Mandatory,ParameterSetName='contents')] [string[]] @@ -324,8 +324,8 @@ function New-RandomHexString $script:CanWriteToPsHome = $null function Test-CanWriteToPsHome { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '', Justification = "an error message is not appropriate for this function")] - param () + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '', Justification = "an error message is not appropriate for this function")] + param () if ($null -ne $script:CanWriteToPsHome) { return $script:CanWriteToPsHome } @@ -476,20 +476,20 @@ function Test-IsWindows2016 { # Ensure that the global:PSDefaultParameterValues variable is a hashtable function Initialize-PSDefaultParameterValue { - if ( $global:PSDefaultParameterValues -isnot [hashtable] ) { - $global:PSDefaultParameterValues = @{} - } + if ( $global:PSDefaultParameterValues -isnot [hashtable] ) { + $global:PSDefaultParameterValues = @{} + } } # reset the stack function Reset-DefaultParameterValueStack { - $script:DefaultParameterValueStack = [system.collections.generic.Stack[hashtable]]::new() + $script:DefaultParameterValueStack = [system.collections.generic.Stack[hashtable]]::new() Initialize-PSDefaultParameterValue } # return the current stack function Get-DefaultParameterValueStack { - $script:DefaultParameterValueStack + $script:DefaultParameterValueStack } # PSDefaultParameterValue may not have both skip and pending keys @@ -507,35 +507,35 @@ function Test-PSDefaultParameterValue { # if $ht is null, then the current value of $global:PSDefaultParameterValues is pushed # if $NewValue is used, then $ht is used as the new value of $global:PSDefaultParameterValues function Push-DefaultParameterValueStack { - param ([hashtable]$ht, [switch]$NewValue) + param ([hashtable]$ht, [switch]$NewValue) Initialize-PSDefaultParameterValue - $script:DefaultParameterValueStack.Push($global:PSDefaultParameterValues.Clone()) - if ( $ht ) { - if ( $NewValue ) { - $global:PSDefaultParameterValues = $ht - } - else { - foreach ($k in $ht.Keys) { - $global:PSDefaultParameterValues[$k] = $ht[$k] - } - } + $script:DefaultParameterValueStack.Push($global:PSDefaultParameterValues.Clone()) + if ( $ht ) { + if ( $NewValue ) { + $global:PSDefaultParameterValues = $ht + } + else { + foreach ($k in $ht.Keys) { + $global:PSDefaultParameterValues[$k] = $ht[$k] + } + } if ( ! (Test-PSDefaultParameterValue)) { Write-Warning -Message "PSDefaultParameterValues may not have both skip and pending keys, resetting." Pop-DefaultParameterValueStack } - } + } } function Pop-DefaultParameterValueStack { - try { - $global:PSDefaultParameterValues = $script:DefaultParameterValueStack.Pop() - return $true - } - catch { + try { + $global:PSDefaultParameterValues = $script:DefaultParameterValueStack.Pop() + return $true + } + catch { Initialize-PSDefaultParameterValue - return $false - } + return $false + } } function Get-HelpNetworkTestCases From 084ae755c6f154b8b2721c05ad16329fc913cedb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:58:53 -0800 Subject: [PATCH 144/255] Bump actions/upload-artifact from 5 to 6 (#26616) --- .github/workflows/macos-ci.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/windows-packaging-reusable.yml | 2 +- .github/workflows/xunit-tests.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 60596c75ed7..8f15a2f3a6d 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -228,7 +228,7 @@ jobs: testResultsFolder: "${{ runner.workspace }}/testResults" - name: Upload package artifact if: always() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: macos-package path: "*.pkg" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index f6ca3ccac1c..1cc99ace9aa 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index f3a46cae908..bb4873adeb3 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -81,7 +81,7 @@ jobs: - name: Upload Build Artifacts if: always() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} path: | diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index 940e1cb839c..5d225446cb7 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -46,7 +46,7 @@ jobs: Write-Host "Completed xUnit test run." - name: Upload xUnit results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 if: always() with: name: ${{ inputs.test_results_artifact_name }} From 4b2e8e00be4fb41f1e005aa45ff636479dd795fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:59:07 +0000 Subject: [PATCH 145/255] Bump github/codeql-action from 4.31.7 to 4.31.8 (#26615) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 55275cbcc3b..3271b534794 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 1cc99ace9aa..3d9ff2eba28 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 + uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 with: sarif_file: results.sarif From 3f54d4af1f0cdd087cdfc0e5cddf06f83cfbb889 Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:33:44 -0800 Subject: [PATCH 146/255] Update package references and build to use the .NET SDK 10.0.101 (#26595) --- DotnetRuntimeMetadata.json | 2 +- global.json | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 6 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 10 +- .../Microsoft.WSMan.Management.csproj | 2 +- .../PSVersionInfoGenerator.csproj | 2 +- .../System.Management.Automation.csproj | 12 +- .../BenchmarkDotNet.Extensions.csproj | 4 +- .../ResultsComparer/ResultsComparer.csproj | 2 +- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- tools/cgmanifest.json | 108 +++++++++--------- 15 files changed, 80 insertions(+), 80 deletions(-) diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index 267f0bda7d6..1ae71bf0737 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -4,7 +4,7 @@ "quality": "daily", "qualityFallback": "preview", "packageVersionPattern": "9.0.0-preview.6", - "sdkImageVersion": "10.0.100", + "sdkImageVersion": "10.0.101", "nextChannel": "9.0.0-preview.7", "azureFeed": "", "sdkImageOverride": "" diff --git a/global.json b/global.json index 376af49c07f..936a420a573 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.100" + "version": "10.0.101" } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index a338c175f14..f63196d3645 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index ef949508346..b2622f0517f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 1f9f342b259..b99139eb68e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -8,7 +8,7 @@ - + @@ -32,8 +32,8 @@ - - + + diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 45b4d8c0a0a..feea5665288 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 87f71daada9..d3446d28c98 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,19 +16,19 @@ - - + + - - + + - + diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 48b4d8ff29c..178f0473f26 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj index 1e53a35f966..1099b2a3c11 100644 --- a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj +++ b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index e929166b20d..542fa8526ff 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,12 +32,12 @@ - - - - - - + + + + + + diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj index 91566f64fb1..92c3f13d290 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index af3fc10ffce..c4831924845 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -9,7 +9,7 @@ - + diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index 15a6e49864c..03aa5697267 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index 1dddf99ba5e..db2fde85955 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ - + diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json index d8165298b22..efbc103f25e 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest.json @@ -26,7 +26,7 @@ "Type": "nuget", "Nuget": { "Name": "Humanizer.Core", - "Version": "2.14.1" + "Version": "3.0.1" } }, "DevelopmentDependency": false @@ -36,7 +36,7 @@ "Type": "nuget", "Nuget": { "Name": "Json.More.Net", - "Version": "2.1.1" + "Version": "2.2.0" } }, "DevelopmentDependency": false @@ -46,7 +46,7 @@ "Type": "nuget", "Nuget": { "Name": "JsonPointer.Net", - "Version": "5.3.1" + "Version": "6.0.0" } }, "DevelopmentDependency": false @@ -66,7 +66,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.43.0" + "Version": "0.44.0" } }, "DevelopmentDependency": false @@ -86,7 +86,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -106,7 +106,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Common", - "Version": "4.14.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -116,7 +116,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.CSharp", - "Version": "4.14.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -126,7 +126,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -166,7 +166,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -176,7 +176,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -186,7 +186,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -206,7 +206,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -216,7 +216,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -226,7 +226,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -236,7 +236,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -246,7 +246,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -256,7 +256,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -266,7 +266,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -276,7 +276,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -286,7 +286,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -296,7 +296,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -306,7 +306,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -316,7 +316,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -326,7 +326,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -336,7 +336,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -356,7 +356,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -366,7 +366,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -376,7 +376,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -436,7 +436,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -446,7 +446,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -456,7 +456,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -466,7 +466,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -476,7 +476,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -486,7 +486,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -506,7 +506,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -516,7 +516,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -526,7 +526,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -536,7 +536,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -546,7 +546,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -556,7 +556,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -566,7 +566,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -576,7 +576,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -586,7 +586,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -596,7 +596,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -606,7 +606,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -616,7 +616,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -626,7 +626,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -636,7 +636,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -646,7 +646,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -656,7 +656,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -706,7 +706,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -716,7 +716,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -726,7 +726,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false @@ -746,7 +746,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "10.0.0" + "Version": "10.0.1" } }, "DevelopmentDependency": false From cd0628641b2ab7c733c060386faf87f29439a833 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 16 Dec 2025 10:50:38 -0800 Subject: [PATCH 147/255] Update `Get-ChangeLog` to handle backport PRs and Copilot PRs correctly (#26610) --- tools/releaseTools.psm1 | 113 +++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/tools/releaseTools.psm1 b/tools/releaseTools.psm1 index b034e2863dd..99edf93c08a 100644 --- a/tools/releaseTools.psm1 +++ b/tools/releaseTools.psm1 @@ -37,6 +37,7 @@ $Script:powershell_team = @( "dependabot-preview[bot]" "dependabot[bot]" "github-actions[bot]" + "Copilot" "Anam Navied" "Andrew Schwartzmeyer" "Jason Helmick" @@ -46,6 +47,27 @@ $Script:powershell_team = @( "Justin Chung" ) +# The powershell team members GitHub logins. We use them to decide if the original author of a backport PR is from the team. +$script:psteam_logins = @( + 'andyleejordan' + 'TravisEz13' + 'daxian-dbw' + 'adityapatwardhan' + 'SteveL-MSFT' + 'dependabot[bot]' + 'pwshBot' + 'jshigetomi' + 'SeeminglyScience' + 'anamnavi' + 'sdwheeler' + 'Copilot' + 'copilot-swe-agent' + 'app/copilot-swe-agent' + 'StevenBucher98' + 'alerickson' + 'tgauth' +) + # They are very active contributors, so we keep their email-login mappings here to save a few queries to Github. $Script:community_login_map = @{ "darpa@yandex.ru" = "iSazonov" @@ -54,11 +76,6 @@ $Script:community_login_map = @{ "info@powercode-consulting.se" = "powercode" } -# Ignore dependency bumping bot (Dependabot): -$Script:attribution_ignore_list = @( - 'dependabot[bot]@users.noreply.github.com' -) - ############################## #.SYNOPSIS #In the release workflow, the release branch will be merged back to master after the release is done, @@ -262,25 +279,76 @@ function Get-ChangeLog $clExperimental = @() foreach ($commit in $new_commits) { + $commitSubject = $commit.Subject + $prNumber = $commit.PullRequest + Write-Verbose "subject: $commitSubject" Write-Verbose "authorname: $($commit.AuthorName)" - if ($commit.AuthorEmail.EndsWith("@microsoft.com") -or $powershell_team -contains $commit.AuthorName -or $Script:attribution_ignore_list -contains $commit.AuthorEmail) { - $commit.ChangeLogMessage = "- {0}" -f (Get-ChangeLogMessage $commit.Subject) + + try { + $pr = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/PowerShell/PowerShell/pulls/$prNumber" ` + -Headers $header ` + -ErrorAction Stop ` + -Verbose:$false ## Always disable verbose to avoid noise when we debug this function. + } catch { + ## A commit may not have corresponding GitHub PRs. In that case, we will get status code 404 (Not Found). + ## Otherwise, let the error bubble up. + if ($_.Exception.Response.StatusCode -ne 404) { + throw + } + } + + if ($commitSubject -match '^\[release/v\d\.\d\] ') { + ## The commit was from a backport PR. We need to get the real author in this case. + if (-not $pr) { + throw "The commit is from a backport PR (#$prNumber), but the PR cannot be found.`nPR Title: $commitSubject" + } + + $userPattern = 'Triggered by @.+ on behalf of @(.+)' + if ($pr.body -match $userPattern) { + $commit.AuthorGitHubLogin = ($Matches.1).Trim() + Write-Verbose "backport PR. real author login: $($commit.AuthorGitHubLogin)" + } else { + throw "The commit is from a backport PR (#$prNumber), but the PR description failed to match the pattern '$userPattern'. Was the template for backport PRs changed?`nPR Title: $commitSubject" + } + } + + if ($commit.AuthorGitHubLogin) { + if ($script:psteam_logins -contains $commit.AuthorGitHubLogin) { + $commit.ChangeLogMessage = "- {0}" -f (Get-ChangeLogMessage $commitSubject) + } else { + $commit.ChangeLogMessage = ("- {0} (Thanks @{1}!)" -f (Get-ChangeLogMessage $commitSubject), $commit.AuthorGitHubLogin) + $commit.ThankYouMessage = ("@{0}" -f ($commit.AuthorGitHubLogin)) + } + } elseif ($commit.AuthorEmail.EndsWith("@microsoft.com") -or $powershell_team -contains $commit.AuthorName) { + $commit.ChangeLogMessage = "- {0}" -f (Get-ChangeLogMessage $commitSubject) } else { if ($community_login_map.ContainsKey($commit.AuthorEmail)) { $commit.AuthorGitHubLogin = $community_login_map[$commit.AuthorEmail] } else { - $uri = "https://api.github.com/repos/PowerShell/PowerShell/commits/$($commit.Hash)" try{ - $response = Invoke-WebRequest -Uri $uri -Method Get -Headers $header -ErrorAction Ignore - } catch{} + ## Always disable verbose to avoid noise when we debug this function. + $response = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/PowerShell/PowerShell/commits/$($commit.Hash)" ` + -Headers $header ` + -ErrorAction Stop ` + -Verbose:$false + } catch { + ## A commit could be available in ADO only. In that case, we will get status code 422 (UnprocessableEntity). + ## Otherwise, let the error bubble up. + if ($_.Exception.Response.StatusCode -ne 422) { + throw + } + } + if($response) { - $content = ConvertFrom-Json -InputObject $response.Content - $commit.AuthorGitHubLogin = $content.author.login + $commit.AuthorGitHubLogin = $response.author.login $community_login_map[$commit.AuthorEmail] = $commit.AuthorGitHubLogin } } - $commit.ChangeLogMessage = ("- {0} (Thanks @{1}!)" -f (Get-ChangeLogMessage $commit.Subject), $commit.AuthorGitHubLogin) + + $commit.ChangeLogMessage = ("- {0} (Thanks @{1}!)" -f (Get-ChangeLogMessage $commitSubject), $commit.AuthorGitHubLogin) $commit.ThankYouMessage = ("@{0}" -f ($commit.AuthorGitHubLogin)) } @@ -289,16 +357,6 @@ function Get-ChangeLog } ## Get the labels for the PR - try { - $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/PowerShell/pulls/$($commit.PullRequest)" -Headers $header -ErrorAction SilentlyContinue - } - catch { - if ($_.Exception.Response.StatusCode -eq '404') { - $pr = $null - #continue - } - } - if($pr) { $clLabel = $pr.labels | Where-Object { $_.Name -match "^CL-"} @@ -328,7 +386,7 @@ function Get-ChangeLog "CL-Tools" { $clTools += $commit } "CL-Untagged" { $clUntagged += $commit } "CL-NotInBuild" { continue } - Default { throw "unknown tag '$cLabel' for PR: '$($commit.PullRequest)'" } + Default { throw "unknown tag '$cLabel' for PR: '$prNumber'" } } } } @@ -426,6 +484,9 @@ function Get-ChangeLogMessage '^Build\(deps\): ' { return $OriginalMessage.replace($Matches.0,'') } + '^\[release/v\d\.\d\] ' { + return $OriginalMessage.replace($Matches.0,'') + } default { return $OriginalMessage } @@ -867,8 +928,8 @@ function Invoke-PRBackport { ) $continue = $false while(!$continue) { - $input= Read-Host -Prompt ($Message + "`nType 'Yes' to continue 'No' to exit") - switch($input) { + $value = Read-Host -Prompt ($Message + "`nType 'Yes' to continue 'No' to exit") + switch($value) { 'yes' { $continue= $true } From b14cd0330c525f48fa12fa03b0ff02d4564c2984 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 16 Dec 2025 12:07:14 -0800 Subject: [PATCH 148/255] Fix the DSC test by skipping `AfterAll` cleanup if the initial setup in `BeforeAll` failed (#26621) --- .../dsc/dsc.profileresource.Tests.ps1 | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/powershell/dsc/dsc.profileresource.Tests.ps1 b/test/powershell/dsc/dsc.profileresource.Tests.ps1 index f361e67ea8e..cb357c7350b 100644 --- a/test/powershell/dsc/dsc.profileresource.Tests.ps1 +++ b/test/powershell/dsc/dsc.profileresource.Tests.ps1 @@ -4,8 +4,10 @@ Describe "DSC PowerShell Profile Resource Tests" -Tag "CI" { BeforeAll { $DSC_ROOT = $env:DSC_ROOT + $skipCleanup = $false if (-not (Test-Path -Path $DSC_ROOT)) { + $skipCleanup = $true throw "DSC_ROOT environment variable is not set or path does not exist." } @@ -40,6 +42,10 @@ Describe "DSC PowerShell Profile Resource Tests" -Tag "CI" { New-Item -Path $testProfilePathCurrentUserAllHosts -Value $testProfileContent -Force -ItemType File } AfterAll { + if ($skipCleanup) { + return + } + # Restore original profile $testProfilePathCurrentUserCurrentHost = $PROFILE.CurrentUserCurrentHost if (Test-Path "$TestDrive/currentuser-currenthost-profile.bak") { @@ -118,16 +124,19 @@ Describe "DSC PowerShell Profile Resource Tests" -Tag "CI" { Describe "DSC PowerShell Profile resource elevated tests" -Tag "CI", 'RequireAdminOnWindows', 'RequireSudoOnUnix' { BeforeAll { $DSC_ROOT = $env:DSC_ROOT + $skipCleanup = $false if (-not (Test-Path -Path $DSC_ROOT)) { + $skipCleanup = $true throw "DSC_ROOT environment variable is not set or path does not exist." } Write-Verbose "DSC_ROOT is set to $DSC_ROOT" -Verbose - $pathSeparator = [System.IO.Path]::PathSeparator - $env:PATH += "$pathSeparator$DSC_ROOT" + $originalPath = $env:PATH + $pathSeparator = [System.IO.Path]::PathSeparator + $env:PATH += "$pathSeparator$DSC_ROOT" $env:PATH += "$pathSeparator$PSHome" Write-Verbose "Updated PATH to include DSC_ROOT: $env:PATH" -Verbose @@ -151,11 +160,12 @@ Describe "DSC PowerShell Profile resource elevated tests" -Tag "CI", 'RequireAdm $testProfilePathAllUsersAllHosts = $PROFILE.AllUsersAllHosts Copy-Item -Path $testProfilePathAllUsersAllHosts -Destination "$TestDrive/allusers-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue New-Item -Path $testProfilePathAllUsersAllHosts -Value $testProfileContent -Force -ItemType File - - $originalPath = $env:PATH - $env:PATH += "$pathSeparator$PSHome" } AfterAll { + if ($skipCleanup) { + return + } + $env:PATH = $originalPath $testProfilePathAllUsersCurrentHost = $PROFILE.AllUsersCurrentHost From d89da9b2fc4043a8b2fca84090b1b5e6d08bdddb Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 16 Dec 2025 15:58:36 -0800 Subject: [PATCH 149/255] Update changelog for the release v7.6.0-preview.6 (#26597) --- CHANGELOG/preview.md | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index 3648f4cc121..f1c5f566289 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,5 +1,76 @@ # Preview Changelog +## [7.6.0-preview.6] - 2025-12-11 + +### Engine Updates and Fixes + +- Properly Expand Aliases to their actual ResolvedCommand (#26571) (Thanks @kilasuit!) + +### General Cmdlet Updates and Fixes + +- Update `Microsoft.PowerShell.PSResourceGet` to `v1.2.0-preview5` (#26590) +- Make the experimental feature `PSFeedbackProvider` stable (#26502) +- Fix a regression in the API `CompletionCompleters.CompleteFilename()` that causes null reference exception (#26487) +- Add Delimiter parameter to `Get-Clipboard` (#26572) (Thanks @MartinGC94!) +- Close pipe client handles after creating the child ssh process (#26564) +- Make some experimental features stable (#26490) +- DSC v3 resource for PowerShell Profile (#26447) + +### Tools + +- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26530) +- Add reusable get-changed-files action and refactor existing actions (#26529) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26494) + +### Tests + +- Fix merge conflict checker for empty file lists and filter *.cs files (#26556) +- Add markdown link verification for PRs (#26445) + +### Build and Packaging Improvements + +
+ + + +

Expand to see details.

+ +
+ +
    +
  • Fix template path for rebuild branch check in package.yml (#26560)
  • +
  • Update the macos package name for preview releases to match the previous pattern (#26576)
  • +
  • Add rebuild branch support with conditional MSIX signing (#26573)
  • +
  • Update the WCF packages to the latest version that is compatible with v4.10.3 (#26503)
  • +
  • Improve ADO package build and validation across platforms (#26532)
  • +
  • Mirror .NET/runtime ICU version range in PowerShell (#26563) (Thanks @kasperk81!)
  • +
  • Update the macos package name for preview releases to match the previous pattern (#26562)
  • +
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26561)
  • +
  • Move package validation to package pipeline (#26558)
  • +
  • Optimize/split windows package signing (#26557)
  • +
  • Remove usage of fpm for DEB package generation (#26504)
  • +
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26524)
  • +
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26501)
  • +
  • Replace fpm with native rpmbuild for RPM package generation (#26441)
  • +
  • Fix GitHub API rate limit errors in test actions (#26492)
  • +
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26493)
  • +
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26488)
  • +
  • Fix build to only enable ready-to-run for the Release configuration (#26481)
  • +
  • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26468)
  • +
  • Update outdated package references (#26471)
  • +
  • GitHub Workflow cleanup (#26439)
  • +
  • Update PSResourceGet package version to preview4 (#26438)
  • +
  • Update PSReadLine to v2.4.5 (#26446)
  • +
  • Add network isolation policy parameter to vPack pipeline (#26444)
  • +
  • Fix a couple more lint errors
  • +
  • Fix lint errors in preview.md
  • +
  • Make MSIX publish stage dependent on SetReleaseTagandContainerName stage
  • +
+ +
+ +[7.6.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.5...v7.6.0-preview.6 + ## [7.6.0-preview.5] - 2025-09-30 ### Engine Updates and Fixes From d0d39c03b12c9b485e739a448ae6a0eaa74d6384 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Wed, 17 Dec 2025 18:15:36 +0900 Subject: [PATCH 150/255] Fix Test-Json false positive errors when using oneOf or anyOf in schema (#26618) --- .../commands/utility/TestJsonCommand.cs | 39 +++- .../Test-Json.Tests.ps1 | 195 ++++++++++++++++++ 2 files changed, 224 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs index 32603f160f8..909cbff3c8f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs @@ -264,19 +264,11 @@ protected override void ProcessRecord() if (_jschema != null) { - EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.List }); + EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical }); result = evaluationResults.IsValid; if (!result) { - HandleValidationErrors(evaluationResults); - - if (evaluationResults.HasDetails) - { - foreach (var nestedResult in evaluationResults.Details) - { - HandleValidationErrors(nestedResult); - } - } + ReportValidationErrors(evaluationResults); } } } @@ -298,6 +290,33 @@ protected override void ProcessRecord() WriteObject(result); } + /// + /// Recursively reports validation errors from hierarchical evaluation results. + /// Skips nodes (and their children) where IsValid is true to avoid false positives + /// from constructs like OneOf or AnyOf. + /// + /// The evaluation result to process. + private void ReportValidationErrors(EvaluationResults evaluationResult) + { + // Skip this node and all children if validation passed + if (evaluationResult.IsValid) + { + return; + } + + // Report errors at this level + HandleValidationErrors(evaluationResult); + + // Recursively process child results + if (evaluationResult.HasDetails) + { + foreach (var nestedResult in evaluationResult.Details) + { + ReportValidationErrors(nestedResult); + } + } + } + private void HandleValidationErrors(EvaluationResults evaluationResult) { if (!evaluationResult.HasErrors) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 index 32a6cf3ce63..7b1b58d8258 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 @@ -86,6 +86,141 @@ Describe "Test-Json" -Tags "CI" { } '@ + # Schema using oneOf to allow either integer or string pattern for port items + $oneOfSchema = @' + { + "type": "object", + "properties": { + "ports": { + "type": "array", + "items": { + "oneOf": [ + { "type": "integer", "minimum": 0, "maximum": 65535 }, + { "type": "string", "pattern": "^\\d+-\\d+$" } + ] + } + } + } + } +'@ + + # Valid JSON where ports are integers (first oneOf choice matches) + $validOneOfJson = '{ "ports": [80, 443, 8080] }' + + # Invalid JSON where a port value matches neither oneOf choice + $invalidOneOfJson = '{ "ports": [80, "invalid-port", 8080] }' + + # Schema using oneOf to allow either smartphone or laptop device types + $oneOfDeviceSchema = @' + { + "type": "object", + "properties": { + "Devices": { + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "properties": { + "id": { "type": "string" }, + "deviceType": { "const": "smartphone" }, + "os": { "type": "string", "enum": ["iOS", "Android"] } + }, + "required": ["deviceType", "os"] + }, + { + "properties": { + "id": { "type": "string" }, + "deviceType": { "const": "laptop" }, + "arch": { "type": "string", "enum": ["x86", "x64", "arm64"] } + }, + "required": ["deviceType", "arch"] + } + ] + } + } + }, + "required": ["Devices"] + } +'@ + + # Valid JSON with mixed device types (all matching their respective oneOf choice) + $validOneOfDeviceJson = @' + { + "Devices": [ + { "id": "0", "deviceType": "laptop", "arch": "x64" }, + { "id": "1", "deviceType": "smartphone", "os": "iOS" }, + { "id": "2", "deviceType": "laptop", "arch": "arm64" }, + { "id": "3", "deviceType": "smartphone", "os": "Android" } + ] + } +'@ + + # Invalid JSON where only Devices/3 has an invalid os value + $invalidOneOfDeviceJson = @' + { + "Devices": [ + { "id": "0", "deviceType": "laptop", "arch": "x64" }, + { "id": "1", "deviceType": "smartphone", "os": "iOS" }, + { "id": "2", "deviceType": "laptop", "arch": "arm64" }, + { "id": "3", "deviceType": "smartphone", "os": "WindowsPhone" } + ] + } +'@ + + # Schema using anyOf to allow either smartphone or laptop device types + $anyOfDeviceSchema = @' + { + "type": "object", + "properties": { + "Devices": { + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "properties": { + "deviceType": { "const": "smartphone" }, + "os": { "type": "string", "enum": ["iOS", "Android"] } + }, + "required": ["deviceType", "os"] + }, + { + "properties": { + "deviceType": { "const": "laptop" }, + "arch": { "type": "string", "enum": ["x86", "x64", "arm64"] } + }, + "required": ["deviceType", "arch"] + } + ] + } + } + }, + "required": ["Devices"] + } +'@ + + # Valid JSON with mixed device types (all matching their respective anyOf choice) + $validAnyOfDeviceJson = @' + { + "Devices": [ + { "deviceType": "laptop", "arch": "x64" }, + { "deviceType": "smartphone", "os": "iOS" } + ] + } +'@ + + # Invalid JSON where only Devices/2 has an invalid os value + $invalidAnyOfDeviceJson = @' + { + "Devices": [ + { "deviceType": "laptop", "arch": "x64" }, + { "deviceType": "smartphone", "os": "iOS" }, + { "deviceType": "smartphone", "os": "WindowsPhone" } + ] + } +'@ + $validJsonPath = Join-Path -Path $TestDrive -ChildPath 'validJson.json' $validLiteralJsonPath = Join-Path -Path $TestDrive -ChildPath "[valid]Json.json" $invalidNodeInJsonPath = Join-Path -Path $TestDrive -ChildPath 'invalidNodeInJson.json' @@ -343,4 +478,64 @@ Describe "Test-Json" -Tags "CI" { # With options should pass ($Json | Test-Json -Option $Options -ErrorAction SilentlyContinue) | Should -BeTrue } + + It "Test-Json does not report false positives for valid oneOf matches" { + $errorVar = $null + $result = Test-Json -Json $validOneOfJson -Schema $oneOfSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeTrue + $errorVar.Count | Should -Be 0 + } + + It "Test-Json reports only relevant errors for invalid oneOf values" { + $errorVar = $null + $result = Test-Json -Json $invalidOneOfJson -Schema $oneOfSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeFalse + # Should report error only for the invalid item, not for valid items + $errorVar.Count | Should -BeGreaterThan 0 + $errorVar[0].Exception.Message | Should -Match "/ports/1" + } + + It "Test-Json does not report false positives for valid oneOf device matches" { + $errorVar = $null + $result = Test-Json -Json $validOneOfDeviceJson -Schema $oneOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeTrue + $errorVar.Count | Should -Be 0 + } + + It "Test-Json reports errors only for the invalid device in oneOf schema" { + $errorVar = $null + $result = Test-Json -Json $invalidOneOfDeviceJson -Schema $oneOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeFalse + # Should not report errors for valid devices (Devices/0, /1, /2) + $falsePositives = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/(0|1|2)' } + $falsePositives.Count | Should -Be 0 + # Should report errors only for the invalid device (Devices/3) + $relevantErrors = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/3' } + $relevantErrors.Count | Should -BeGreaterThan 0 + } + + It "Test-Json does not report false positives for valid anyOf device matches" { + $errorVar = $null + $result = Test-Json -Json $validAnyOfDeviceJson -Schema $anyOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeTrue + $errorVar.Count | Should -Be 0 + } + + It "Test-Json reports errors only for the invalid device in anyOf schema" { + $errorVar = $null + $result = Test-Json -Json $invalidAnyOfDeviceJson -Schema $anyOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeFalse + # Should not report errors for valid devices (Devices/0, /1) + $falsePositives = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/(0|1)' } + $falsePositives.Count | Should -Be 0 + # Should report errors only for the invalid device (Devices/2) + $relevantErrors = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/2' } + $relevantErrors.Count | Should -BeGreaterThan 0 + } } From 9ef5ec4734313193f248818c5fb4b43803e06c43 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:22:06 -0600 Subject: [PATCH 151/255] Bring release changes from the v7.6.0-preview.6 release branch (#26627) 1. Fix the conditions used in `release-MSIX-Publish.yml` 2. Update `build.psm1` to not install dotnet format tool for ADO build. --- .pipelines/templates/release-MSIX-Publish.yml | 4 ++-- build.psm1 | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml index 1735cd9f710..2bf1e130103 100644 --- a/.pipelines/templates/release-MSIX-Publish.yml +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -100,7 +100,7 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 displayName: 'Publish StoreBroker Package (Stable/LTS)' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq('$(STABLE)', 'true'), eq('$(LTS)', 'true'))) + condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true'))) inputs: serviceEndpoint: 'StoreAppPublish-Stable' appId: '$(AppID)' @@ -113,7 +113,7 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 displayName: 'Publish StoreBroker Package (Preview)' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq('$(PREVIEW)', 'true')) + condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['PREVIEW'], 'true')) inputs: serviceEndpoint: 'StoreAppPublish-Preview' appId: '$(AppID)' diff --git a/build.psm1 b/build.psm1 index dd2cf0f351e..d09b7af925d 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2485,19 +2485,21 @@ function Start-PSBootstrap { Write-LogGroupEnd -Title "Install Windows Dependencies" } - if ($Scenario -in 'All', 'Tools') { - Write-LogGroupStart -Title "Install .NET Global Tools" - Write-Log -message "Installing .NET global tools" + # Ensure dotnet is available + Find-Dotnet - # Ensure dotnet is available - Find-Dotnet + if (-not $env:TF_BUILD) { + if ($Scenario -in 'All', 'Tools') { + Write-LogGroupStart -Title "Install .NET Global Tools" + Write-Log -message "Installing .NET global tools" - # Install dotnet-format - Write-Verbose -Verbose "Installing dotnet-format global tool" - Start-NativeExecution { - dotnet tool install --global dotnet-format + # Install dotnet-format + Write-Verbose -Verbose "Installing dotnet-format global tool" + Start-NativeExecution { + dotnet tool install --global dotnet-format + } + Write-LogGroupEnd -Title "Install .NET Global Tools" } - Write-LogGroupEnd -Title "Install .NET Global Tools" } if ($env:TF_BUILD) { From 1df30e0c6d12018f0a20064cd522400d182f01fc Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 5 Jan 2026 11:35:14 -0500 Subject: [PATCH 152/255] Update SECURITY.md to remove email reporting option (#26653) --- .github/SECURITY.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index f941d308b1f..797f7003851 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -12,9 +12,7 @@ If you believe you have found a security vulnerability in any Microsoft-owned re Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). +You should receive a response within 24 hours. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: From 867d799e6fadffa0d2676faa328ad37151904528 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 6 Jan 2026 10:27:45 -0500 Subject: [PATCH 153/255] Add default CODEOWNERS entry for maintainers (#26660) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6de13fd8daf..14569b81924 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,9 @@ # Areas are not limited to the filters defined in this file # First, let's start with areas with no filters or paths +# Default +* @PowerShell/powershell-maintainers + # Area: Performance # @adityapatwardhan From 4838a8d5f6c95339d4b44c8b402165e69e6ff929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:44:22 -0800 Subject: [PATCH 154/255] Bump github/codeql-action from 4.31.8 to 4.31.9 (#26623) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 3271b534794..07a63c2e348 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3d9ff2eba28..9b09c232801 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 with: sarif_file: results.sarif From 7ace9e0f5e998c02151b5264148dacc58de76f87 Mon Sep 17 00:00:00 2001 From: Jonathan Gilbert Date: Fri, 9 Jan 2026 17:48:37 -0600 Subject: [PATCH 155/255] Improve error message from `Start-NativeExecution` (#26500) --- tools/buildCommon/startNativeExecution.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/buildCommon/startNativeExecution.ps1 b/tools/buildCommon/startNativeExecution.ps1 index ee7b00d04cd..2e44d86bd6a 100644 --- a/tools/buildCommon/startNativeExecution.ps1 +++ b/tools/buildCommon/startNativeExecution.ps1 @@ -16,6 +16,8 @@ function script:Start-NativeExecution { $ErrorActionPreference = "Continue" Write-Verbose "Executing: $ScriptBlock" try { + $cwd = Get-Location + if ($VerboseOutputOnError.IsPresent) { $output = & $ScriptBlock 2>&1 } else { @@ -36,10 +38,10 @@ function script:Start-NativeExecution { $callerFile = $callerLocationParts[0] $callerLine = $callerLocationParts[1] - $errorMessage = "Execution of {$ScriptBlock} by ${callerFile}: line $callerLine failed with exit code $LASTEXITCODE" + $errorMessage = "Execution of {$ScriptBlock} in '$cwd' by ${callerFile}: line $callerLine failed with exit code $LASTEXITCODE" throw $errorMessage } - throw "Execution of {$ScriptBlock} failed with exit code $LASTEXITCODE" + throw "Execution of {$ScriptBlock} in '$cwd' failed with exit code $LASTEXITCODE" } } finally { $ErrorActionPreference = $backupEAP From 5ebba3ca331be57baf8cdc0d7021e7f3ff3678d7 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 12 Jan 2026 13:05:19 -0500 Subject: [PATCH 156/255] Delete unused winget release script (#26683) --- tools/releaseToWinget.ps1 | 166 -------------------------------------- 1 file changed, 166 deletions(-) delete mode 100644 tools/releaseToWinget.ps1 diff --git a/tools/releaseToWinget.ps1 b/tools/releaseToWinget.ps1 deleted file mode 100644 index e3c1af080e3..00000000000 --- a/tools/releaseToWinget.ps1 +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -param( - [Parameter(Mandatory)] - [semver] - $ReleaseVersion, - - [Parameter()] - [string] - $WingetRepoPath = "$PSScriptRoot/../../winget-pkgs", - - [Parameter()] - [string] - $FromRepository = 'rjmholt', - - [Parameter()] - [string] - $GitHubToken -) - -function GetMsiHash -{ - param( - [Parameter(Mandatory)] - [string] - $ReleaseVersion, - - [Parameter(Mandatory)] - $MsiName - ) - - $releaseParams = @{ - Tag = "v$ReleaseVersion" - OwnerName = 'PowerShell' - RepositoryName = 'PowerShell' - } - - if ($GitHubToken) { $releaseParams.AccessToken = $GitHubToken } - - $releaseDescription = (Get-GitHubRelease @releaseParams).body - - $regex = [regex]::new("powershell-$ReleaseVersion-win-x64.msi.*?([0-9A-F]{64})", 'SingleLine,IgnoreCase') - - return $regex.Match($releaseDescription).Groups[1].Value -} - -function GetThisScriptRepoUrl -{ - # Find the root of the repo - $prefix = $PSScriptRoot - while ($prefix) - { - if (Test-Path "$prefix/LICENSE.txt") - { - break - } - - $prefix = Split-Path $prefix - } - - $stem = $PSCommandPath.Substring($prefix.Length + 1).Replace('\', '/') - - return "https://github.com/PowerShell/PowerShell/blob/master/$stem" -} - -function Exec -{ - param([scriptblock]$sb) - - & $sb - - if ($LASTEXITCODE -ne 0) - { - throw "Invocation failed for '$sb'. See above errors for details" - } -} - -$ErrorActionPreference = 'Stop' - -$wingetPath = (Resolve-Path $WingetRepoPath).Path - -# Ensure we have git and PowerShellForGitHub installed -Import-Module -Name PowerShellForGitHub -$null = Get-Command git - -# Get the MSI hash from the release body -$msiName = "PowerShell-$ReleaseVersion-win-x64.msi" -$msiHash = GetMsiHash -ReleaseVersion $ReleaseVersion -MsiName $msiName - -$publisherName = 'Microsoft' - -# Create the manifest -$productName = if ($ReleaseVersion.PreReleaseLabel) -{ - 'PowerShell-Preview' -} -else -{ - 'PowerShell' -} - -$manifestDir = Join-Path $wingetPath 'manifests' 'm' $publisherName $productName $ReleaseVersion -$manifestPath = Join-Path $manifestDir "$publisherName.$productName.yaml" - -$manifestContent = @" -PackageIdentifier: $publisherName.$productName -PackageVersion: $ReleaseVersion -PackageName: $productName -Publisher: $publisherName -PackageUrl: https://microsoft.com/PowerShell -License: MIT -LicenseUrl: https://github.com/PowerShell/PowerShell/blob/master/LICENSE.txt -Moniker: $($productName.ToLower()) -ShortDescription: $publisherName.$productName -Description: PowerShell is a cross-platform (Windows, Linux, and macOS) automation and configuration tool/framework that works well with your existing tools and is optimized for dealing with structured data (e.g. JSON, CSV, XML, etc.), REST APIs, and object models. It includes a command-line shell, an associated scripting language and a framework for processing cmdlets. -Tags: -- powershell -- pwsh -Homepage: https://github.com/PowerShell/PowerShell -Installers: -- Architecture: x64 - InstallerUrl: https://github.com/PowerShell/PowerShell/releases/download/v$ReleaseVersion/$msiName - InstallerSha256: $msiHash - InstallerType: msi -PackageLocale: en-US -ManifestType: singleton -ManifestVersion: 1.0.0 - -"@ - -Push-Location $wingetPath -try -{ - $branch = "pwsh-$ReleaseVersion" - - Exec { git checkout master } - Exec { git checkout -b $branch } - - New-Item -Path $manifestDir -ItemType Directory - Set-Content -Path $manifestPath -Value $manifestContent -Encoding utf8NoBOM - - Exec { git add $manifestPath } - Exec { git commit -m "Add $productName $ReleaseVersion" } - Exec { git push origin $branch } - - $prParams = @{ - Title = "Add $productName $ReleaseVersion" - Body = "This pull request is automatically generated. See $(GetThisScriptRepoUrl)." - Head = $branch - HeadOwner = $FromRepository - Base = 'master' - Owner = 'Microsoft' - RepositoryName = 'winget-pkgs' - MaintainerCanModify = $true - } - - if ($GitHubToken) { $prParams.AccessToken = $GitHubToken } - - New-GitHubPullRequest @prParams -} -finally -{ - git checkout master - Pop-Location -} From bf03a14b6dcf6f399451f45b01460c6a7280de1d Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 12 Jan 2026 10:44:55 -0800 Subject: [PATCH 157/255] Fix the CLR internal error and null ref exception when running `show-command` with PowerShell API (#26669) --- .../commandHelpers/ShowCommandHelper.cs | 3 ++- .../resources/HelpErrors.resx | 2 +- .../utils/GraphicalHostReflectionWrapper.cs | 13 +++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs index 32c68e961c6..10690b65dc7 100644 --- a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs @@ -679,7 +679,8 @@ internal static string SingleQuote(string str) /// The host window, if it is present or null if it is not. internal static Window GetHostWindow(PSCmdlet cmdlet) { - PSPropertyInfo windowProperty = cmdlet.Host.PrivateData.Properties["Window"]; + // The value of 'PrivateData' property may be null for the default host or a custom host. + PSPropertyInfo windowProperty = cmdlet.Host.PrivateData?.Properties["Window"]; if (windowProperty == null) { return null; diff --git a/src/System.Management.Automation/resources/HelpErrors.resx b/src/System.Management.Automation/resources/HelpErrors.resx index 27634de2995..ae797e8af12 100644 --- a/src/System.Management.Automation/resources/HelpErrors.resx +++ b/src/System.Management.Automation/resources/HelpErrors.resx @@ -179,7 +179,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator" command, and try running Update-Help again. - To use the {0}, install Windows PowerShell ISE by using Server Manager, and then restart this application. ({1}) + To use the {0}, make sure your application uses 'Microsoft.NET.Sdk.WindowsDesktop' as the project SDK and the corresponding assembly 'Microsoft.PowerShell.GraphicalHost' is available. ({1}) {0} does not work in a remote session. diff --git a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs index a0b12f986de..ec780222345 100644 --- a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs +++ b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs @@ -55,7 +55,7 @@ private GraphicalHostReflectionWrapper() /// When it was not possible to load Microsoft.PowerShell.GraphicalHost.dlly. internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper(PSCmdlet parentCmdlet, string graphicalHostHelperTypeName) { - return GraphicalHostReflectionWrapper.GetGraphicalHostReflectionWrapper(parentCmdlet, graphicalHostHelperTypeName, parentCmdlet.CommandInfo.Name); + return GetGraphicalHostReflectionWrapper(parentCmdlet, graphicalHostHelperTypeName, parentCmdlet.CommandInfo.Name); } /// @@ -73,9 +73,9 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Assembly.Load has been found to throw unadvertised exceptions")] internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper(PSCmdlet parentCmdlet, string graphicalHostHelperTypeName, string featureName) { - GraphicalHostReflectionWrapper returnValue = new GraphicalHostReflectionWrapper(); + GraphicalHostReflectionWrapper returnValue = new(); - if (GraphicalHostReflectionWrapper.IsInputFromRemoting(parentCmdlet)) + if (IsInputFromRemoting(parentCmdlet)) { ErrorRecord error = new ErrorRecord( new NotSupportedException(StringUtil.Format(HelpErrors.RemotingNotSupportedForFeature, featureName)), @@ -87,9 +87,10 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper } // Prepare the full assembly name. - AssemblyName graphicalHostAssemblyName = new AssemblyName(); + AssemblyName smaAssemblyName = typeof(PSObject).Assembly.GetName(); + AssemblyName graphicalHostAssemblyName = new(); graphicalHostAssemblyName.Name = "Microsoft.PowerShell.GraphicalHost"; - graphicalHostAssemblyName.Version = new Version(3, 0, 0, 0); + graphicalHostAssemblyName.Version = smaAssemblyName.Version; graphicalHostAssemblyName.CultureInfo = new CultureInfo(string.Empty); // Neutral culture graphicalHostAssemblyName.SetPublicKeyToken(new byte[] { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 }); @@ -124,7 +125,7 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper returnValue._graphicalHostHelperType = returnValue._graphicalHostAssembly.GetType(graphicalHostHelperTypeName); - Diagnostics.Assert(returnValue._graphicalHostHelperType != null, "the type exists in Microsoft.PowerShell.GraphicalHost"); + Diagnostics.Assert(returnValue._graphicalHostHelperType != null, "the type should exist in Microsoft.PowerShell.GraphicalHost"); ConstructorInfo constructor = returnValue._graphicalHostHelperType.GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, From c792b3c0ba609776ce6eba308495f76601b2c04f Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 12 Jan 2026 10:45:41 -0800 Subject: [PATCH 158/255] Fix to use accurate message for validating a string argument is not null and not an empty string (#26668) --- .../CommandCompletion/CompletionResult.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs index ffb09e2a50d..0554a52ba17 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs @@ -168,26 +168,15 @@ internal static CompletionResult Null /// The text for the tooltip with details to be displayed about the object. public CompletionResult(string completionText, string listItemText, CompletionResultType resultType, string toolTip) { - if (string.IsNullOrEmpty(completionText)) - { - throw PSTraceSource.NewArgumentNullException(nameof(completionText)); - } - - if (string.IsNullOrEmpty(listItemText)) - { - throw PSTraceSource.NewArgumentNullException(nameof(listItemText)); - } + ArgumentException.ThrowIfNullOrEmpty(completionText); + ArgumentException.ThrowIfNullOrEmpty(listItemText); + ArgumentException.ThrowIfNullOrEmpty(toolTip); if (resultType < CompletionResultType.Text || resultType > CompletionResultType.DynamicKeyword) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(resultType), resultType); } - if (string.IsNullOrEmpty(toolTip)) - { - throw PSTraceSource.NewArgumentNullException(nameof(toolTip)); - } - _completionText = completionText; _listItemText = listItemText; _toolTip = toolTip; From 9bc47950e20d1d85710dbf424270e54a649fd3ac Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 12 Jan 2026 10:47:41 -0800 Subject: [PATCH 159/255] Add `ValidateNotNullOrEmpty` attribute to the `-Property` of `Format-Table/List/Custom` (#26552) --- .../FormatAndOutput/format-object/Format-Object.cs | 1 + .../common/BaseFormattingCommand.cs | 1 + test/powershell/engine/Formatting/BugFix.Tests.ps1 | 14 ++++++++++++++ 3 files changed, 16 insertions(+) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs index c2c16ff2cd1..693a799c809 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs @@ -32,6 +32,7 @@ public FormatCustomCommand() /// will be determined using property sets, etc. /// [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] public object[] Property { get { return _props; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs index 683553a3b30..be31a1db9a8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs @@ -728,6 +728,7 @@ public class OuterFormatTableAndListBase : OuterFormatShapeCommandBase /// will be determined using property sets, etc. ///
[Parameter(Position = 0)] + [ValidateNotNullOrEmpty] public object[] Property { get; set; } /// diff --git a/test/powershell/engine/Formatting/BugFix.Tests.ps1 b/test/powershell/engine/Formatting/BugFix.Tests.ps1 index 5be7797134c..8b1ecdaccd9 100644 --- a/test/powershell/engine/Formatting/BugFix.Tests.ps1 +++ b/test/powershell/engine/Formatting/BugFix.Tests.ps1 @@ -63,3 +63,17 @@ Describe "Hidden properties should not be returned by the 'FirstOrDefault' primi $outstring.Trim() | Should -BeExactly "Param$([System.Environment]::NewLine)-----$([System.Environment]::NewLine)Foo" } } + +Describe "'Format-Table/List/Custom -Property' should not throw NullRef exception" -Tag CI { + + It "' -Property' requires value to be not null and not empty" -TestCases @( + @{ Command = "Format-Table"; NameInErrorId = "FormatTableCommand" } + @{ Command = "Format-List"; NameInErrorId = "FormatListCommand" } + @{ Command = "Format-Custom"; NameInErrorId = "FormatCustomCommand" } + ) { + param($Command, $NameInErrorId) + + { Get-Process -Id $PID | & $Command -Property @() } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.$NameInErrorId" + { Get-Process -Id $PID | & $Command -Property $null } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.$NameInErrorId" + } +} From 20382c85654caa53d2403db17857952775cf7ecd Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:50:04 -0800 Subject: [PATCH 160/255] Update outdated package references (#26656) --- test/xUnit/xUnit.tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 0863a23d441..de5b04b8c6c 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -24,12 +24,12 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + From 76bc34e08114639bf2640cc0a708fac900f1e160 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:26:44 -0800 Subject: [PATCH 161/255] Bump github/codeql-action from 4.31.9 to 4.31.10 (#26686) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 07a63c2e348..04c5b6cb58f 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 + uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 + uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 9b09c232801..87b593164a4 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 + uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.29.5 with: sarif_file: results.sarif From 1241ad2794cc84c18554e36ce658bfd86781cf8e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:58:38 -0500 Subject: [PATCH 162/255] Remove unused runCodesignValidationInjection variable from pipeline templates (#26412) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk (HE/HIM) --- .pipelines/templates/compliance/apiscan.yml | 2 -- .pipelines/templates/compliance/generateNotice.yml | 2 -- .pipelines/templates/linux-package-build.yml | 2 -- .pipelines/templates/linux.yml | 4 ---- .pipelines/templates/mac-package-build.yml | 2 -- .pipelines/templates/mac.yml | 2 -- .pipelines/templates/nupkg.yml | 2 -- .pipelines/templates/packaging/windows/package.yml | 2 -- .pipelines/templates/release-symbols.yml | 2 -- .pipelines/templates/release-upload-buildinfo.yml | 2 -- .pipelines/templates/testartifacts.yml | 4 ---- .pipelines/templates/uploadToAzure.yml | 2 -- .pipelines/templates/variable/release-shared.yml | 2 -- .pipelines/templates/windows-hosted-build.yml | 2 -- 14 files changed, 32 deletions(-) diff --git a/.pipelines/templates/compliance/apiscan.yml b/.pipelines/templates/compliance/apiscan.yml index 5809af8e28c..b5a15699026 100644 --- a/.pipelines/templates/compliance/apiscan.yml +++ b/.pipelines/templates/compliance/apiscan.yml @@ -4,8 +4,6 @@ jobs: - job: APIScan variables: - - name: runCodesignValidationInjection - value : false - name: NugetSecurityAnalysisWarningLevel value: none - name: ReleaseTagVar diff --git a/.pipelines/templates/compliance/generateNotice.yml b/.pipelines/templates/compliance/generateNotice.yml index 7de316e8b49..0a38ed8f8e6 100644 --- a/.pipelines/templates/compliance/generateNotice.yml +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -10,8 +10,6 @@ parameters: jobs: - job: generateNotice variables: - - name: runCodesignValidationInjection - value : false - name: NugetSecurityAnalysisWarningLevel value: none - name: ob_outputDirectory diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml index bcf332b3778..ea49b9fea5d 100644 --- a/.pipelines/templates/linux-package-build.yml +++ b/.pipelines/templates/linux-package-build.yml @@ -13,8 +13,6 @@ jobs: type: linux variables: - - name: runCodesignValidationInjection - value: false - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel diff --git a/.pipelines/templates/linux.yml b/.pipelines/templates/linux.yml index aef0b53381f..97b594830b3 100644 --- a/.pipelines/templates/linux.yml +++ b/.pipelines/templates/linux.yml @@ -10,8 +10,6 @@ jobs: pool: type: linux variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - name: DOTNET_NOLOGO @@ -139,8 +137,6 @@ jobs: pool: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - name: DOTNET_NOLOGO diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index 4e7040d4acc..def866173ac 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -15,8 +15,6 @@ jobs: variables: - name: HOMEBREW_NO_ANALYTICS value: 1 - - name: runCodesignValidationInjection - value: false - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel diff --git a/.pipelines/templates/mac.yml b/.pipelines/templates/mac.yml index ff4787b9bcf..1699207c657 100644 --- a/.pipelines/templates/mac.yml +++ b/.pipelines/templates/mac.yml @@ -13,8 +13,6 @@ jobs: variables: - name: HOMEBREW_NO_ANALYTICS value: 1 - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess diff --git a/.pipelines/templates/nupkg.yml b/.pipelines/templates/nupkg.yml index 3a2aa4f3172..3558c949402 100644 --- a/.pipelines/templates/nupkg.yml +++ b/.pipelines/templates/nupkg.yml @@ -6,8 +6,6 @@ jobs: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml index 03673d61c7b..1f03d65ab21 100644 --- a/.pipelines/templates/packaging/windows/package.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -9,8 +9,6 @@ jobs: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: ob_sdl_codeSignValidation_enabled value: false # Skip signing validation in build-only stage - name: ob_signing_setup_enabled diff --git a/.pipelines/templates/release-symbols.yml b/.pipelines/templates/release-symbols.yml index 1023dcf5259..a628f4d7127 100644 --- a/.pipelines/templates/release-symbols.yml +++ b/.pipelines/templates/release-symbols.yml @@ -10,8 +10,6 @@ jobs: pool: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - name: DOTNET_NOLOGO diff --git a/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml index c18c96fc646..e32272c6306 100644 --- a/.pipelines/templates/release-upload-buildinfo.yml +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -14,8 +14,6 @@ jobs: demands: - ImageOverride -equals PSMMS2019-Secure variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - name: DOTNET_NOLOGO diff --git a/.pipelines/templates/testartifacts.yml b/.pipelines/templates/testartifacts.yml index 751c9d5a53b..3a6bec4a859 100644 --- a/.pipelines/templates/testartifacts.yml +++ b/.pipelines/templates/testartifacts.yml @@ -1,8 +1,6 @@ jobs: - job: build_testartifacts_win variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess @@ -73,8 +71,6 @@ jobs: - job: build_testartifacts_nonwin variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess diff --git a/.pipelines/templates/uploadToAzure.yml b/.pipelines/templates/uploadToAzure.yml index 20842de81cc..ce7f26131cc 100644 --- a/.pipelines/templates/uploadToAzure.yml +++ b/.pipelines/templates/uploadToAzure.yml @@ -7,8 +7,6 @@ jobs: variables: - name: ob_sdl_sbom_enabled value: true - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - name: DOTNET_NOLOGO diff --git a/.pipelines/templates/variable/release-shared.yml b/.pipelines/templates/variable/release-shared.yml index 325f72224f5..70d3dd2df97 100644 --- a/.pipelines/templates/variable/release-shared.yml +++ b/.pipelines/templates/variable/release-shared.yml @@ -17,8 +17,6 @@ variables: value: false - name: ob_sdl_sbom_enabled value: ${{ parameters.SBOM }} - - name: runCodesignValidationInjection - value: false - name: DOTNET_NOLOGO value: 1 - group: 'mscodehub-code-read-akv' diff --git a/.pipelines/templates/windows-hosted-build.yml b/.pipelines/templates/windows-hosted-build.yml index 44b83ad20aa..83c6b32cdfd 100644 --- a/.pipelines/templates/windows-hosted-build.yml +++ b/.pipelines/templates/windows-hosted-build.yml @@ -10,8 +10,6 @@ jobs: pool: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - name: DOTNET_NOLOGO From bd33c2d9878a46af65968da42fc4411bcd7bc46d Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 14 Jan 2026 14:38:37 -0800 Subject: [PATCH 163/255] Update metadata.json to update the Latest attribute with a better name (#26380) --- .pipelines/templates/channelSelection.yml | 8 ++++---- .pipelines/templates/release-prep-for-ev2.yml | 8 ++++---- .pipelines/templates/release-upload-buildinfo.yml | 4 ++-- tools/metadata.json | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.pipelines/templates/channelSelection.yml b/.pipelines/templates/channelSelection.yml index 9dd0f3fb216..d6ddb53256e 100644 --- a/.pipelines/templates/channelSelection.yml +++ b/.pipelines/templates/channelSelection.yml @@ -2,6 +2,10 @@ steps: - pwsh: | # Determine LTS, Preview, or Stable $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json + + $LTS = $metadata.LTSRelease.PublishToChannels + $Stable = $metadata.StableRelease.PublishToChannels + $isPreview = '$(OutputReleaseTag.releaseTag)' -match '-' $releaseTag = '$(OutputReleaseTag.releaseTag)' # Rebuild branches should be treated as preview builds @@ -11,10 +15,6 @@ steps: # If you update this regex, also update it in rebuild-branch-check.yml to keep them in sync. $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' - $LTS = $metadata.LTSRelease.Latest - $Stable = $metadata.StableRelease.Latest - $isPreview = $releaseTag -match '-' - # If this is a rebuild branch, force preview mode and ignore LTS metadata if ($isRebuildBranch) { $IsLTS = $false diff --git a/.pipelines/templates/release-prep-for-ev2.yml b/.pipelines/templates/release-prep-for-ev2.yml index cf7982cd5e1..e644bece68f 100644 --- a/.pipelines/templates/release-prep-for-ev2.yml +++ b/.pipelines/templates/release-prep-for-ev2.yml @@ -33,7 +33,7 @@ stages: - template: release-SetReleaseTagandContainerName.yml parameters: restorePhase: true - + - pwsh: | $packageVersion = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '^v','' $vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion" @@ -42,7 +42,7 @@ stages: displayName: Set Package version env: ob_restore_phase: true - + - pwsh: | $branch = 'mirror-target' $gitArgs = "clone", @@ -151,7 +151,7 @@ stages: $metadataHash = @{} $skipPublishValue = '${{ parameters.skipPublish }}' $metadataHash["ReleaseTag"] = '$(OutputReleaseTag.ReleaseTag)' - $metadataHash["LTS"] = $metadata.LTSRelease.Latest + $metadataHash["LTS"] = $metadata.LTSRelease.PublishToChannels $metadataHash["ForProduction"] = $true $metadataHash["SkipPublish"] = [System.Convert]::ToBoolean($skipPublishValue) @@ -222,7 +222,7 @@ stages: files_to_sign: '*.ps1' search_root: '$(repoRoot)/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run' displayName: Sign Run.ps1 - + - pwsh: | # folder to tar must have: Run.ps1, settings.toml, python_dl $srcPath = Join-Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'Shell' diff --git a/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml index e32272c6306..bcb2a43396f 100644 --- a/.pipelines/templates/release-upload-buildinfo.yml +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -56,8 +56,8 @@ jobs: $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) $metadata = Get-Content -LiteralPath "$toolsDirectory/metadata.json" -ErrorAction Stop | ConvertFrom-Json - $stableRelease = $metadata.StableRelease.Latest - $ltsRelease = $metadata.LTSRelease.Latest + $stableRelease = $metadata.StableRelease.PublishToChannels + $ltsRelease = $metadata.LTSRelease.PublishToChannels Write-Verbose -Verbose "Writing $jsonFile contents:" $buildInfoJsonContent = Get-Content $jsonFile -Encoding UTF8NoBom -Raw diff --git a/tools/metadata.json b/tools/metadata.json index bbe64d0cefe..e0f4952c588 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -5,6 +5,6 @@ "ReleaseTag": "v7.5.4", "LTSReleaseTag" : ["v7.4.13"], "NextReleaseTag": "v7.6.0-preview.6", - "LTSRelease": { "Latest": false, "Package": false }, - "StableRelease": { "Latest": false, "Package": false } + "LTSRelease": { "PublishToChannels": false, "Package": false }, + "StableRelease": { "PublishToChannels": false, "Package": false } } From 5fdc2a023f9591bc467cdeff17dfaea6820f40bd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:40:58 -0500 Subject: [PATCH 164/255] Fix macOS preview package identifier detection to use version string (#26690) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- test/packaging/packaging.tests.ps1 | 67 +++++++++++++++++++++++++++++ tools/packaging/packaging.psm1 | 69 ++++++++++++++++++++++-------- 2 files changed, 119 insertions(+), 17 deletions(-) create mode 100644 test/packaging/packaging.tests.ps1 diff --git a/test/packaging/packaging.tests.ps1 b/test/packaging/packaging.tests.ps1 new file mode 100644 index 00000000000..cc4bbe0d728 --- /dev/null +++ b/test/packaging/packaging.tests.ps1 @@ -0,0 +1,67 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Packaging Module Functions" { + BeforeAll { + Import-Module $PSScriptRoot/../../build.psm1 -Force + Import-Module $PSScriptRoot/../../tools/packaging/packaging.psm1 -Force + } + + Context "Test-IsPreview function" { + It "Should return True for preview versions" { + Test-IsPreview -Version "7.6.0-preview.6" | Should -Be $true + Test-IsPreview -Version "7.5.0-rc.1" | Should -Be $true + } + + It "Should return False for stable versions" { + Test-IsPreview -Version "7.6.0" | Should -Be $false + Test-IsPreview -Version "7.5.0" | Should -Be $false + } + + It "Should return False for LTS builds regardless of version string" { + Test-IsPreview -Version "7.6.0-preview.6" -IsLTS | Should -Be $false + Test-IsPreview -Version "7.5.0" -IsLTS | Should -Be $false + } + } + + Context "Get-MacOSPackageIdentifierInfo function (New-MacOSPackage logic)" { + It "Should detect preview builds and return preview identifier" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.6.0-preview.6" -LTS:$false + + $result.IsPreview | Should -Be $true + $result.PackageIdentifier | Should -Be "com.microsoft.powershell-preview" + } + + It "Should detect stable builds and return stable identifier" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.6.0" -LTS:$false + + $result.IsPreview | Should -Be $false + $result.PackageIdentifier | Should -Be "com.microsoft.powershell" + } + + It "Should treat LTS builds as stable even with preview version string" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.4.0-preview.1" -LTS:$true + + $result.IsPreview | Should -Be $false + $result.PackageIdentifier | Should -Be "com.microsoft.powershell" + } + + It "Should NOT use package name for preview detection (bug fix verification)" { + # This test verifies the fix for issue #26673 + # The bug was using ($Name -like '*-preview') which always returned false + # because preview builds use Name="powershell" not "powershell-preview" + + $Version = "7.6.0-preview.6" + $Name = "powershell" # Preview builds use "powershell" not "powershell-preview" + + # The INCORRECT logic (the bug): $Name -like '*-preview' + $incorrectCheck = $Name -like '*-preview' + $incorrectCheck | Should -Be $false -Because "Package name is 'powershell' not 'powershell-preview'" + + # The CORRECT logic (the fix): uses version string + $result = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$false + $result.IsPreview | Should -Be $true -Because "Version string correctly identifies preview" + $result.PackageIdentifier | Should -Be "com.microsoft.powershell-preview" + } + } +} diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 8f9cec71790..cddde65aa9f 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1371,6 +1371,7 @@ function New-UnixPackage { AppsFolder = $AppsFolder HostArchitecture = $HostArchitecture CurrentLocation = $CurrentLocation + LTS = $LTS } try { @@ -1515,7 +1516,12 @@ function New-MacOsDistributionPackage # Get package ID if not provided if (-not $PackageIdentifier) { - $PackageIdentifier = Get-MacOSPackageId -IsPreview:$IsPreview.IsPresent + if ($IsPreview.IsPresent) { + $PackageIdentifier = 'com.microsoft.powershell-preview' + } + else { + $PackageIdentifier = 'com.microsoft.powershell' + } } # Minimum OS version @@ -1984,7 +1990,9 @@ function New-MacOSPackage [Parameter(Mandatory)] [string]$HostArchitecture, - [string]$CurrentLocation = (Get-Location) + [string]$CurrentLocation = (Get-Location), + + [switch]$LTS ) Write-Log "Creating macOS package using pkgbuild and productbuild..." @@ -2059,8 +2067,10 @@ function New-MacOSPackage Copy-Item -Path "$AppsFolder/*" -Destination $appsInPkg -Recurse -Force } - # Build the component package using pkgbuild - $pkgIdentifier = Get-MacOSPackageId -IsPreview:($Name -like '*-preview') + # Get package identifier info based on version and LTS flag + $packageInfo = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$LTS + $IsPreview = $packageInfo.IsPreview + $pkgIdentifier = $packageInfo.PackageIdentifier if ($PSCmdlet.ShouldProcess("Build component package with pkgbuild")) { Write-Log "Running pkgbuild to create component package..." @@ -2085,7 +2095,7 @@ function New-MacOSPackage -OutputDirectory $CurrentLocation ` -HostArchitecture $HostArchitecture ` -PackageIdentifier $pkgIdentifier ` - -IsPreview:($Name -like '*-preview') + -IsPreview:$IsPreview return $distributionPackage } @@ -2292,20 +2302,44 @@ function New-ManGzip } } -# Returns the macOS Package Identifier -function Get-MacOSPackageId +<# + .SYNOPSIS + Determines the package identifier and preview status for macOS packages. + .DESCRIPTION + This function determines if a package is a preview build based on the version string + and LTS flag, then returns the appropriate package identifier. + .PARAMETER Version + The version string (e.g., "7.6.0-preview.6" or "7.6.0") + .PARAMETER LTS + Whether this is an LTS build + .OUTPUTS + Hashtable with IsPreview (boolean) and PackageIdentifier (string) properties + .EXAMPLE + Get-MacOSPackageIdentifierInfo -Version "7.6.0-preview.6" -LTS:$false + Returns @{ IsPreview = $true; PackageIdentifier = "com.microsoft.powershell-preview" } +#> +function Get-MacOSPackageIdentifierInfo { param( - [switch] - $IsPreview + [Parameter(Mandatory)] + [string]$Version, + + [switch]$LTS ) - if ($IsPreview.IsPresent) - { - return 'com.microsoft.powershell-preview' + + $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS + + # Determine package identifier based on preview status + if ($IsPreview) { + $PackageIdentifier = 'com.microsoft.powershell-preview' } - else - { - return 'com.microsoft.powershell' + else { + $PackageIdentifier = 'com.microsoft.powershell' + } + + return @{ + IsPreview = $IsPreview + PackageIdentifier = $PackageIdentifier } } @@ -2319,8 +2353,9 @@ function New-MacOSLauncher [switch]$LTS ) - $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS - $packageId = Get-MacOSPackageId -IsPreview:$IsPreview + $packageInfo = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$LTS + $IsPreview = $packageInfo.IsPreview + $packageId = $packageInfo.PackageIdentifier # Define folder for launcher application. $suffix = if ($IsPreview) { "-preview" } elseif ($LTS) { "-lts" } From 276989fa55e1e48279467719d873657e337f63a6 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jan 2026 14:36:04 -0500 Subject: [PATCH 165/255] Update Microsoft.PowerShell.PSResourceGet version to 1.2.0-rc1 (#26691) --- src/Modules/PSGalleryModules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index b6e5506e0b0..cbc47ef200c 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -13,7 +13,7 @@ - + From 0931a75d3e4a177c2f1691e50d4124ca229f234b Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:26:59 -0600 Subject: [PATCH 166/255] Fix `buildinfo.json` uploading for preview, LTS, and stable releases (#25571) --- .../templates/release-upload-buildinfo.yml | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml index bcb2a43396f..c470af1fd6e 100644 --- a/.pipelines/templates/release-upload-buildinfo.yml +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -56,6 +56,9 @@ jobs: $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) $metadata = Get-Content -LiteralPath "$toolsDirectory/metadata.json" -ErrorAction Stop | ConvertFrom-Json + $stableReleaseTag = $metadata.StableReleaseTag -Replace 'v','' + + $currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v','' $stableRelease = $metadata.StableRelease.PublishToChannels $ltsRelease = $metadata.LTSRelease.PublishToChannels @@ -65,40 +68,44 @@ jobs: $buildInfo = $buildInfoJsonContent | ConvertFrom-Json $buildInfo.ReleaseDate = $dateTime + $currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v','' $targetFile = "$ENV:PIPELINE_WORKSPACE/$fileName" ConvertTo-Json -InputObject $buildInfo | Out-File $targetFile -Encoding ascii - if ($stableRelease -or $fileName -eq "preview.json") { - Set-BuildVariable -Name CopyMainBuildInfo -Value YES + if ($fileName -eq "preview.json") { + Set-BuildVariable -Name UploadPreview -Value YES } else { - Set-BuildVariable -Name CopyMainBuildInfo -Value NO + Set-BuildVariable -Name UploadPreview -Value NO } - Set-BuildVariable -Name BuildInfoJsonFile -Value $targetFile - - ## Create 'lts.json' if it's the latest stable and also a LTS release. + Set-BuildVariable -Name PreviewBuildInfoFile -Value $targetFile + ## Create 'lts.json' if marked as a LTS release. if ($fileName -eq "stable.json") { + [System.Management.Automation.SemanticVersion] $stableVersion = $stableReleaseTag + [System.Management.Automation.SemanticVersion] $currentVersion = $currentReleaseTag if ($ltsRelease) { $ltsFile = "$ENV:PIPELINE_WORKSPACE/lts.json" Copy-Item -Path $targetFile -Destination $ltsFile -Force - Set-BuildVariable -Name LtsBuildInfoJsonFile -Value $ltsFile - Set-BuildVariable -Name CopyLTSBuildInfo -Value YES + Set-BuildVariable -Name LTSBuildInfoFile -Value $ltsFile + Set-BuildVariable -Name UploadLTS -Value YES } else { - Set-BuildVariable -Name CopyLTSBuildInfo -Value NO + Set-BuildVariable -Name UploadLTS -Value NO } - $releaseTag = $buildInfo.ReleaseTag - $version = $releaseTag -replace '^v' - $semVersion = [System.Management.Automation.SemanticVersion] $version + ## Only update the stable.json if the current version is greater than the stable version. + if ($currentVersion -gt $stableVersion) { + $versionFile = "$ENV:PIPELINE_WORKSPACE/$($currentVersion.Major)-$($currentVersion.Minor).json" + Copy-Item -Path $targetFile -Destination $versionFile -Force + Set-BuildVariable -Name StableBuildInfoFile -Value $versionFile + Set-BuildVariable -Name UploadStable -Value YES + } else { + Set-BuildVariable -Name UploadStable -Value NO + } - $versionFile = "$ENV:PIPELINE_WORKSPACE/$($semVersion.Major)-$($semVersion.Minor).json" - Copy-Item -Path $targetFile -Destination $versionFile -Force - Set-BuildVariable -Name VersionBuildInfoJsonFile -Value $versionFile - Set-BuildVariable -Name CopyVersionBuildInfo -Value YES } else { - Set-BuildVariable -Name CopyVersionBuildInfo -Value NO + Set-BuildVariable -Name UploadStable -Value NO } displayName: Create json files @@ -116,24 +123,27 @@ jobs: $storageContext = New-AzStorageContext -StorageAccountName $storageAccount -UseConnectedAccount - if ($env:CopyMainBuildInfo -eq 'YES') { - $jsonFile = "$env:BuildInfoJsonFile" + #preview + if ($env:UploadPreview -eq 'YES') { + $jsonFile = "$env:PreviewBuildInfoFile" $blobName = Get-Item $jsonFile | Split-Path -Leaf Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - if ($env:CopyLTSBuildInfo -eq 'YES') { - $jsonFile = "$env:LtsBuildInfoJsonFile" + #LTS + if ($env:UploadLTS -eq 'YES') { + $jsonFile = "$env:LTSBuildInfoFile" $blobName = Get-Item $jsonFile | Split-Path -Leaf Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - if ($env:CopyVersionBuildInfo -eq 'YES') { - $jsonFile = "$env:VersionBuildInfoJsonFile" + #stable + if ($env:UploadStable -eq 'YES') { + $jsonFile = "$env:StableBuildInfoFile" $blobName = Get-Item $jsonFile | Split-Path -Leaf Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - condition: and(succeeded(), eq(variables['CopyMainBuildInfo'], 'YES')) + condition: and(succeeded(), or(eq(variables['UploadPreview'], 'YES'), eq(variables['UploadLTS'], 'YES'), eq(variables['UploadStable'], 'YES'))) From dab346a6b7426e3b7bb4a074ac5b856129d4692d Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:50:23 -0800 Subject: [PATCH 167/255] Move PowerShell build to depend on .NET SDK 10.0.102 (#26697) Co-authored-by: Dongbo Wang --- global.json | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 10 +- .../Microsoft.WSMan.Management.csproj | 2 +- .../System.Management.Automation.csproj | 12 +-- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- test/xUnit/xUnit.tests.csproj | 2 +- tools/cgmanifest.json | 102 +++++++++--------- 12 files changed, 71 insertions(+), 71 deletions(-) diff --git a/global.json b/global.json index 936a420a573..c2af57a3fe4 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.101" + "version": "10.0.102" } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index f63196d3645..32e0d329886 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index b2622f0517f..7d9d61ede96 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index b99139eb68e..5ee999c2d9c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index feea5665288..78232a2f1af 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index d3446d28c98..161170355e6 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,19 +16,19 @@ - - + + - - + + - + diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 178f0473f26..da5ea7ddd04 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 542fa8526ff..9b5e21811cf 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,12 +32,12 @@ - - - - - - + + + + + + diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index 03aa5697267..af62ecaacab 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index db2fde85955..cbb01a67da5 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ - + diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index de5b04b8c6c..6ce2ee32ceb 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -29,7 +29,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json index efbc103f25e..1001370ade7 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest.json @@ -1,5 +1,4 @@ { - "$schema": "https://json.schemastore.org/component-detection-manifest.json", "Registrations": [ { "Component": { @@ -46,7 +45,7 @@ "Type": "nuget", "Nuget": { "Name": "JsonPointer.Net", - "Version": "6.0.0" + "Version": "6.0.1" } }, "DevelopmentDependency": false @@ -86,7 +85,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -126,7 +125,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -166,7 +165,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -176,7 +175,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -186,7 +185,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -206,7 +205,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -216,7 +215,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -226,7 +225,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -236,7 +235,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -246,7 +245,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -256,7 +255,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -266,7 +265,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -276,7 +275,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -286,7 +285,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -296,7 +295,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -306,7 +305,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -316,7 +315,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -326,7 +325,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -336,7 +335,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -356,7 +355,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -366,7 +365,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -376,7 +375,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -436,7 +435,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -446,7 +445,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -456,7 +455,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -466,7 +465,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -476,7 +475,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -486,7 +485,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -506,7 +505,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -516,7 +515,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -526,7 +525,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -536,7 +535,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -546,7 +545,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -556,7 +555,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -566,7 +565,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -576,7 +575,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -586,7 +585,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -596,7 +595,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -606,7 +605,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -616,7 +615,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -626,7 +625,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -636,7 +635,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -646,7 +645,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -656,7 +655,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -706,7 +705,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -716,7 +715,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -726,7 +725,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false @@ -746,10 +745,11 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "10.0.1" + "Version": "10.0.2" } }, "DevelopmentDependency": false } - ] + ], + "$schema": "https://json.schemastore.org/component-detection-manifest.json" } From f5c290ccd6463d63968dda734a282c10a5b499ec Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 22 Jan 2026 04:52:08 +1000 Subject: [PATCH 168/255] Fix WSManInstance COM interface with ResourceURI (#26692) --- src/Microsoft.WSMan.Management/Interop.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.WSMan.Management/Interop.cs b/src/Microsoft.WSMan.Management/Interop.cs index e5dd462e2f7..3abb938a572 100644 --- a/src/Microsoft.WSMan.Management/Interop.cs +++ b/src/Microsoft.WSMan.Management/Interop.cs @@ -734,18 +734,19 @@ public interface IWSManResourceLocator [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] string ResourceUri { - // IDL: HRESULT resourceUri ([out, retval] BSTR* ReturnValue); + // IDL: HRESULT resourceUri (BSTR value); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] [DispId(1)] - [return: MarshalAs(UnmanagedType.BStr)] - get; + set; - // IDL: HRESULT resourceUri (BSTR value); + // IDL: HRESULT resourceUri ([out, retval] BSTR* ReturnValue); [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] [DispId(1)] - set; + [return: MarshalAs(UnmanagedType.BStr)] + get; } /// AddSelector method of IWSManResourceLocator interface. Add selector to resource locator @@ -818,14 +819,16 @@ string FragmentDialect int MustUnderstandOptions { - // IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue); - - [DispId(7)] - get; // IDL: HRESULT MustUnderstandOptions (long value); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")] [DispId(7)] set; + + // IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue); + + [DispId(7)] + get; } /// ClearOptions method of IWSManResourceLocator interface. Clear all options From 7f9c7463f203393f06cbc82f4154d1d8420019bb Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 22 Jan 2026 04:55:42 +1000 Subject: [PATCH 169/255] Support TargetObject position in ParserErrors (#26649) --- .../PowerShellCore_format_ps1xml.cs | 29 ++ .../engine/Formatting/ErrorView.Tests.ps1 | 381 ++++++++++++++++++ 2 files changed, 410 insertions(+) diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index ef0d506d122..24d99d4dd4b 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -1190,8 +1190,37 @@ function Get-ConciseViewPositionMessage { $highlightLine = '' if ($useTargetObject) { $line = $_.TargetObject.LineText.Trim() + $offsetLength = 0 $offsetInLine = 0 + $startColumn = 0 + if ( + ([System.Collections.IDictionary]$_.TargetObject).Contains('StartColumn') -and + [System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.StartColumn, [ref]$startColumn) -and + $null -ne $startColumn -and + $startColumn -gt 0 -and + $startColumn -le $line.Length + ) { + $endColumn = 0 + if (-not ( + ([System.Collections.IDictionary]$_.TargetObject).Contains('EndColumn') -and + [System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.EndColumn, [ref]$endColumn) -and + $null -ne $endColumn -and + $endColumn -gt $startColumn -and + $endColumn -le ($line.Length + 1) + )) { + $endColumn = $line.Length + 1 + } + + # Input is expected to be 1-based index to match the extent positioning + # but we use 0-based indexing below. + $startColumn -= 1 + $endColumn -= 1 + + $highlightLine = "$(" " * $startColumn)$("~" * ($endColumn - $startColumn))" + $offsetLength = $endColumn - $startColumn + $offsetInLine = $startColumn + } } else { $positionMessage = $myinv.PositionMessage.Split($newline) diff --git a/test/powershell/engine/Formatting/ErrorView.Tests.ps1 b/test/powershell/engine/Formatting/ErrorView.Tests.ps1 index 519b9403b9e..6af2b5fb504 100644 --- a/test/powershell/engine/Formatting/ErrorView.Tests.ps1 +++ b/test/powershell/engine/Formatting/ErrorView.Tests.ps1 @@ -165,6 +165,387 @@ Describe 'Tests for $ErrorView' -Tag CI { $e | Out-String | Should -BeLike '*ParserError*' } + It 'Parser TargetObject shows Line information' { + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject shows File information' { + $expected = (@( + ": MyFile.ps1" + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + File = 'MyFile.ps1' + Line = 1 + LineText = 'This is the line with the error' + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject has StartColumn' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~~~~~~~~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 18 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject has StartColumn and EndColumn' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 18 + EndColumn = 22 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject has StartColumn at end of the line' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 31 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + + It 'Parser TargetObject has StartColumn at end of the line with EndColumn' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 31 + EndColumn = 32 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores EndColumn if no StartColumn' { + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + EndColumn = 22 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject converts StartColumn and EndColumn from string' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = "18" + EndColumn = "22" + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores StartColumn if it cannot be converted' { + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + StartColumn = 'abc' + EndColumn = 22 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores EndColumn if it cannot be converted' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~~~~~~~~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 18 + EndColumn = 'abc' + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores StartColumn with invalid value ' -TestCases @( + @{ Value = -1 } + @{ Value = 0 } + @{ Value = 32 } # Beyond end of line + ) { + param ($Value) + + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + StartColumn = $Value + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores EndColumn with invalid value ' -TestCases @( + @{ Value = -1 } + @{ Value = 0 } + @{ Value = 17 } # Before StartColumn + @{ Value = 18 } # Equal to StartColumn + @{ Value = 33 } # Beyond end of line + 1 + ) { + param ($Value) + + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | ~~~~~~~~~~~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + StartColumn = 18 + EndColumn = $Value + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + It 'Exception thrown from Enumerator.MoveNext in a pipeline shows information' { $e = { $l = [System.Collections.Generic.List[string]] @('one', 'two') From f57643fb9f9dab8e0a6afdad8dc0f5921375cc85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:10:48 -0500 Subject: [PATCH 170/255] Bump github/codeql-action from 4.31.10 to 4.32.0 (#26726) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 04c5b6cb58f..44000b71d5f 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.29.5 + uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.29.5 + uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 87b593164a4..f2c806750a6 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.29.5 + uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 with: sarif_file: results.sarif From b3a38645af47133e2b19d8a5bdf535c9f2333b64 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:02:48 -0800 Subject: [PATCH 171/255] Add policy to restrict the `Approved-LowRisk` label (#26728) --- .../policies/labelAdded.approvedLowRisk.yml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/policies/labelAdded.approvedLowRisk.yml diff --git a/.github/policies/labelAdded.approvedLowRisk.yml b/.github/policies/labelAdded.approvedLowRisk.yml new file mode 100644 index 00000000000..bdeea5265a0 --- /dev/null +++ b/.github/policies/labelAdded.approvedLowRisk.yml @@ -0,0 +1,48 @@ +id: labelAdded.approvedLowRisk +name: GitOps.PullRequestIssueManagement +description: Remove Approved-LowRisk if applied by an unauthorized user +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Remove Approved-LowRisk if label was added by someone not authorized + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: Approved-LowRisk + # Unauthorized = NOT admin AND NOT in explicit allowlist + - not: + or: + - activitySenderHasPermission: + permission: Admin + + # Allowlist (enabled) + - isActivitySender: + user: iSazonov + issueAuthor: False + - isActivitySender: + user: daxian-dbw + issueAuthor: False + + # Allowlist (commented out for now) + # - isActivitySender: + # user: TravisEz13 + # issueAuthor: False + # - isActivitySender: + # user: adityapatwardhan + # issueAuthor: False + # - isActivitySender: + # user: jshigetomi + # issueAuthor: False + then: + - removeLabel: + label: Approved-LowRisk + - addReply: + reply: >- + The `Approved-LowRisk` label is restricted to authorized maintainers and was removed. +onFailure: +onSuccess: From 8489f8fc34669170c5099d0aacd9d3fc8b3ea255 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Thu, 29 Jan 2026 07:06:15 +0900 Subject: [PATCH 172/255] Mark `-NoTypeInformation` as obsolete no-op and evaluate `-IncludeTypeInformation` on by value on Csv cmdlets (#26719) --- .../commands/utility/CsvCommands.cs | 22 +++++-------------- .../resources/CsvCommandStrings.resx | 3 --- .../ConvertTo-Csv.Tests.ps1 | 5 ----- .../Export-Csv.Tests.ps1 | 5 ----- 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index e84a79b99b6..cb455417531 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -45,17 +45,19 @@ public abstract class BaseCsvWritingCommand : PSCmdlet public abstract PSObject InputObject { get; set; } /// - /// IncludeTypeInformation : The #TYPE line should be generated. Default is false. Cannot specify with NoTypeInformation. + /// IncludeTypeInformation : The #TYPE line should be generated. Default is false. /// [Parameter] [Alias("ITI")] public SwitchParameter IncludeTypeInformation { get; set; } /// - /// NoTypeInformation : The #TYPE line should not be generated. Default is true. Cannot specify with IncludeTypeInformation. + /// Gets or sets a value indicating whether to suppress the #TYPE line. + /// This parameter is obsolete and has no effect. It is retained for backward compatibility only. /// [Parameter(DontShow = true)] [Alias("NTI")] + [Obsolete("This parameter is obsolete and has no effect. The default behavior is to not include type information. Use -IncludeTypeInformation to include type information.")] public SwitchParameter NoTypeInformation { get; set; } = true; /// @@ -120,18 +122,6 @@ protected override void BeginProcessing() this.ThrowTerminatingError(errorRecord); } - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation)) && this.MyInvocation.BoundParameters.ContainsKey(nameof(NoTypeInformation))) - { - InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyIncludeTypeInformationAndNoTypeInformation); - ErrorRecord errorRecord = new(exception, "CannotSpecifyIncludeTypeInformationAndNoTypeInformation", ErrorCategory.InvalidData, null); - this.ThrowTerminatingError(errorRecord); - } - - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation))) - { - NoTypeInformation = !IncludeTypeInformation; - } - Delimiter = ImportExportCSVHelper.SetDelimiter(this, ParameterSetName, Delimiter, UseCulture); } } @@ -317,7 +307,7 @@ protected override void ProcessRecord() // write headers (row1: typename + row2: column names) if (!_isActuallyAppending && !NoHeader.IsPresent) { - if (NoTypeInformation == false) + if (IncludeTypeInformation) { WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); } @@ -742,7 +732,7 @@ protected override void ProcessRecord() if (!NoHeader.IsPresent) { - if (NoTypeInformation == false) + if (IncludeTypeInformation) { WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx index 8c5ded13465..dde1be6ab49 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx @@ -130,9 +130,6 @@ You must specify either the -UseQuotes or -QuoteFields parameters, but not both. - - You must specify either the -IncludeTypeInformation or -NoTypeInformation parameters, but not both. - You must specify either the -Path or -LiteralPath parameters, but not both. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 index 298b8140468..bfba7718272 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 @@ -101,11 +101,6 @@ Describe "ConvertTo-Csv" -Tags "CI" { Should -Throw -ErrorId "CannotSpecifyQuoteFieldsAndUseQuotes,Microsoft.PowerShell.Commands.ConvertToCsvCommand" } - It "Does not support -IncludeTypeInformation and -NoTypeInformation at the same time" { - { $testObject | ConvertTo-Csv -IncludeTypeInformation -NoTypeInformation } | - Should -Throw -ErrorId "CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ConvertToCsvCommand" - } - Context "QuoteFields parameter" { It "QuoteFields" { # Use 'FiRstCoLumn' to test case insensitivity diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 index ea05d66d392..aae457f2f82 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 @@ -91,11 +91,6 @@ Describe "Export-Csv" -Tags "CI" { $results[0] | Should -BeExactly "#TYPE System.String" } - It "Does not support -IncludeTypeInformation and -NoTypeInformation at the same time" { - { $testObject | Export-Csv -Path $testCsv -IncludeTypeInformation -NoTypeInformation } | - Should -Throw -ErrorId "CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ExportCsvCommand" - } - It "Should support -LiteralPath parameter" { $testObject | Export-Csv -LiteralPath $testCsv $results = Import-Csv -Path $testCsv From 34f6e267a4a779616f0f441ee034dbf4747c482b Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 2 Feb 2026 19:45:16 -0500 Subject: [PATCH 173/255] Add CodeQL suppressions for NativeCommandProcessor (#26729) --- .../engine/NativeCommandProcessor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 75e3b468814..145fe968fda 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -831,6 +831,7 @@ private void InitNativeProcess() bool useSpecialArgumentPassing = UseSpecialArgumentPassing(oldFileName); if (useSpecialArgumentPassing) { + // codeql[cs/microsoft/command-line-injection] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method and the path portion of the argument is escaped. The user assumes trust for the file path specified on the user's system to start process for, and in the case of remoting, restricted remoting security guidelines should be used. startInfo.Arguments = "\"" + oldFileName + "\" " + startInfo.Arguments; } else @@ -855,7 +856,7 @@ private void InitNativeProcess() startInfo.ArgumentList.RemoveAt(0); } - // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. startInfo.FileName = oldFileName; } } @@ -1607,7 +1608,7 @@ private ProcessStartInfo GetProcessStartInfo( { var startInfo = new ProcessStartInfo { - // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. FileName = this.Path }; From 467059c9cbe3e40fb1026312b8c3f9711260938f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:46:51 -0500 Subject: [PATCH 174/255] Bump github/codeql-action from 4.32.0 to 4.32.1 (#26741) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 44000b71d5f..b56d2d6635c 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 + uses: github/codeql-action/init@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 + uses: github/codeql-action/analyze@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index f2c806750a6..d1d974f8efa 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 + uses: github/codeql-action/upload-sarif@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v3.29.5 with: sarif_file: results.sarif From d54977441f96cebb2b93fbb2364010d02fb00ffa Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 4 Feb 2026 11:38:48 -0800 Subject: [PATCH 175/255] Update `Microsoft.PowerShell.Native` package version (#26746) --- .../System.Management.Automation.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 9b5e21811cf..8c77c770a8f 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -40,7 +40,7 @@ - + From dd4f2fecfaf2e9dc1fbb8831fa47c8b0907c03a3 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Fri, 6 Feb 2026 08:51:22 +0900 Subject: [PATCH 176/255] Add comprehensive scalar type tests for `ConvertTo-Json` (#26736) --- .../ConvertTo-Json.Tests.ps1 | 429 ++++++++++++++++++ 1 file changed, 429 insertions(+) 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 1f2abe05c68..4ac0818faf4 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 @@ -156,4 +156,433 @@ Describe 'ConvertTo-Json' -tags "CI" { $actual = ConvertTo-Json -Compress -InputObject $obj $actual | Should -Be '{"Positive":18446744073709551615,"Negative":-18446744073709551615}' } + #region Comprehensive Scalar Type Tests (Phase 1) + # Test coverage for ConvertTo-Json scalar serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, all primitive and special types + + Context 'Primitive scalar types' { + It 'Should serialize value correctly via Pipeline and InputObject' -TestCases @( + # Byte types + @{ TypeName = 'byte'; Value = [byte]0; Expected = '0' } + @{ TypeName = 'byte'; Value = [byte]255; Expected = '255' } + @{ TypeName = 'sbyte'; Value = [sbyte]-128; Expected = '-128' } + @{ TypeName = 'sbyte'; Value = [sbyte]127; Expected = '127' } + # Short types + @{ TypeName = 'short'; Value = [short]-32768; Expected = '-32768' } + @{ TypeName = 'short'; Value = [short]32767; Expected = '32767' } + @{ TypeName = 'ushort'; Value = [ushort]0; Expected = '0' } + @{ TypeName = 'ushort'; Value = [ushort]65535; Expected = '65535' } + # Integer types + @{ TypeName = 'int'; Value = 42; Expected = '42' } + @{ TypeName = 'int'; Value = -42; Expected = '-42' } + @{ TypeName = 'int'; Value = 0; Expected = '0' } + @{ TypeName = 'int'; Value = [int]::MaxValue; Expected = '2147483647' } + @{ TypeName = 'int'; Value = [int]::MinValue; Expected = '-2147483648' } + @{ TypeName = 'uint'; Value = [uint]0; Expected = '0' } + @{ TypeName = 'uint'; Value = [uint]::MaxValue; Expected = '4294967295' } + # Long types + @{ TypeName = 'long'; Value = [long]::MaxValue; Expected = '9223372036854775807' } + @{ TypeName = 'long'; Value = [long]::MinValue; Expected = '-9223372036854775808' } + @{ TypeName = 'ulong'; Value = [ulong]0; Expected = '0' } + @{ TypeName = 'ulong'; Value = [ulong]::MaxValue; Expected = '18446744073709551615' } + # Floating-point types + @{ TypeName = 'float'; Value = [float]3.14; Expected = '3.14' } + @{ TypeName = 'float'; Value = [float]::NaN; Expected = '"NaN"' } + @{ TypeName = 'float'; Value = [float]::PositiveInfinity; Expected = '"Infinity"' } + @{ TypeName = 'float'; Value = [float]::NegativeInfinity; Expected = '"-Infinity"' } + @{ TypeName = 'double'; Value = 3.14159; Expected = '3.14159' } + @{ TypeName = 'double'; Value = -3.14159; Expected = '-3.14159' } + @{ TypeName = 'double'; Value = 0.0; Expected = '0.0' } + @{ TypeName = 'double'; Value = [double]::NaN; Expected = '"NaN"' } + @{ TypeName = 'double'; Value = [double]::PositiveInfinity; Expected = '"Infinity"' } + @{ TypeName = 'double'; Value = [double]::NegativeInfinity; Expected = '"-Infinity"' } + @{ TypeName = 'decimal'; Value = 123.456d; Expected = '123.456' } + # BigInteger + @{ TypeName = 'BigInteger'; Value = 18446744073709551615n; Expected = '18446744073709551615' } + # Boolean + @{ TypeName = 'bool'; Value = $true; Expected = 'true' } + @{ TypeName = 'bool'; Value = $false; Expected = 'false' } + # Null + @{ TypeName = 'null'; Value = $null; Expected = 'null' } + ) { + param($TypeName, $Value, $Expected) + $jsonPipeline = $Value | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $Value -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should include ETS properties on ' -TestCases @( + @{ TypeName = 'int'; Value = 42; Expected = '{"value":42,"MyProp":"test"}' } + @{ TypeName = 'double'; Value = 3.14; Expected = '{"value":3.14,"MyProp":"test"}' } + ) { + param($TypeName, $Value, $Expected) + $valueWithEts = Add-Member -InputObject $Value -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $valueWithEts | ConvertTo-Json -Compress + $json | Should -BeExactly $Expected + } + } + + Context 'String scalar types' { + It 'Should serialize string correctly via Pipeline and InputObject' -TestCases @( + @{ Description = 'regular'; Value = 'hello'; Expected = '"hello"' } + @{ Description = 'empty'; Value = ''; Expected = '""' } + @{ Description = 'with spaces'; Value = 'hello world'; Expected = '"hello world"' } + @{ Description = 'with newline'; Value = "line1`nline2"; Expected = '"line1\nline2"' } + @{ Description = 'with tab'; Value = "col1`tcol2"; Expected = '"col1\tcol2"' } + @{ Description = 'with quotes'; Value = 'say "hello"'; Expected = '"say \"hello\""' } + @{ Description = 'with backslash'; Value = 'c:\path'; Expected = '"c:\\path"' } + @{ Description = 'unicode'; Value = '???'; Expected = '"???"' } + @{ Description = 'emoji'; Value = '??'; Expected = '"??"' } + ) { + param($Description, $Value, $Expected) + $jsonPipeline = $Value | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $Value -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should ignore ETS properties on string' { + $str = Add-Member -InputObject 'hello' -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $str | ConvertTo-Json -Compress + $json | Should -BeExactly '"hello"' + } + } + + Context 'DateTime and related types' { + It 'Should serialize DateTime with UTC kind via Pipeline and InputObject' { + $dt = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) + $jsonPipeline = $dt | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dt -Compress + $jsonPipeline | Should -BeExactly '"2024-06-15T10:30:00Z"' + $jsonInputObject | Should -BeExactly '"2024-06-15T10:30:00Z"' + } + + It 'Should serialize DateTime with Local kind' { + $dt = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Local) + $json = $dt | ConvertTo-Json -Compress + $offset = $dt.ToString('zzz') + $expected = '"2024-06-15T10:30:00' + $offset + '"' + $json | Should -BeExactly $expected + } + + It 'Should serialize DateTime with Unspecified kind via Pipeline and InputObject' { + $dt = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Unspecified) + $jsonPipeline = $dt | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dt -Compress + $jsonPipeline | Should -BeExactly '"2024-06-15T10:30:00"' + $jsonInputObject | Should -BeExactly '"2024-06-15T10:30:00"' + } + + It 'Should serialize DateTimeOffset correctly via Pipeline and InputObject' { + $dto = [DateTimeOffset]::new(2024, 6, 15, 10, 30, 0, [TimeSpan]::FromHours(9)) + $jsonPipeline = $dto | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dto -Compress + $jsonPipeline | Should -BeExactly '"2024-06-15T10:30:00+09:00"' + $jsonInputObject | Should -BeExactly '"2024-06-15T10:30:00+09:00"' + } + + It 'Should serialize DateOnly as object with properties' { + $d = [DateOnly]::new(2024, 6, 15) + $json = $d | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Year":2024,"Month":6,"Day":15,"DayOfWeek":6,"DayOfYear":167,"DayNumber":739051}' + } + + It 'Should serialize TimeOnly as object with properties' { + $t = [TimeOnly]::new(10, 30, 45) + $json = $t | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Hour":10,"Minute":30,"Second":45,"Millisecond":0,"Microsecond":0,"Nanosecond":0,"Ticks":378450000000}' + } + + It 'Should serialize TimeSpan as object with properties' { + $ts = [TimeSpan]::new(1, 2, 3, 4, 5) + $json = $ts | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Ticks":937840050000,"Days":1,"Hours":2,"Milliseconds":5,"Microseconds":0,"Nanoseconds":0,"Minutes":3,"Seconds":4,"TotalDays":1.0854630208333333,"TotalHours":26.0511125,"TotalMilliseconds":93784005.0,"TotalMicroseconds":93784005000.0,"TotalNanoseconds":93784005000000.0,"TotalMinutes":1563.06675,"TotalSeconds":93784.005}' + } + + It 'Should ignore ETS properties on DateTime' { + $dt = [DateTime]::new(2024, 6, 15, 0, 0, 0, [DateTimeKind]::Utc) + $dt = Add-Member -InputObject $dt -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $dt | ConvertTo-Json -Compress + $json | Should -BeExactly '"2024-06-15T00:00:00Z"' + } + + It 'Should include ETS properties on DateTimeOffset' { + $dto = [DateTimeOffset]::new(2024, 6, 15, 10, 30, 0, [TimeSpan]::Zero) + $dto = Add-Member -InputObject $dto -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $dto | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"2024-06-15T10:30:00+00:00","MyProp":"test"}' + } + + It 'Should include ETS properties on DateOnly' { + $d = [DateOnly]::new(2024, 6, 15) + $d = Add-Member -InputObject $d -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $d | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Year":2024,"Month":6,"Day":15,"DayOfWeek":6,"DayOfYear":167,"DayNumber":739051,"MyProp":"test"}' + } + + It 'Should include ETS properties on TimeOnly' { + $t = [TimeOnly]::new(10, 30, 45) + $t = Add-Member -InputObject $t -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $t | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Hour":10,"Minute":30,"Second":45,"Millisecond":0,"Microsecond":0,"Nanosecond":0,"Ticks":378450000000,"MyProp":"test"}' + } + } + + Context 'Guid type' { + It 'Should serialize Guid as string via InputObject' { + $guid = [Guid]::new('12345678-1234-1234-1234-123456789abc') + $json = ConvertTo-Json -InputObject $guid -Compress + $json | Should -BeExactly '"12345678-1234-1234-1234-123456789abc"' + } + + It 'Should serialize Guid with Extended properties via Pipeline' { + $guid = [Guid]::new('12345678-1234-1234-1234-123456789abc') + $json = $guid | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"12345678-1234-1234-1234-123456789abc","Guid":"12345678-1234-1234-1234-123456789abc"}' + } + + It 'Should serialize empty Guid correctly via InputObject' { + $json = ConvertTo-Json -InputObject ([Guid]::Empty) -Compress + $json | Should -BeExactly '"00000000-0000-0000-0000-000000000000"' + } + + It 'Should include ETS properties on Guid via Pipeline' { + $guid = [Guid]::new('12345678-1234-1234-1234-123456789abc') + $guid = Add-Member -InputObject $guid -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $guid | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"12345678-1234-1234-1234-123456789abc","MyProp":"test","Guid":"12345678-1234-1234-1234-123456789abc"}' + } + } + + Context 'Uri type' { + It 'Should serialize Uri correctly via Pipeline and InputObject' -TestCases @( + @{ Description = 'http'; UriString = 'http://example.com'; Expected = '"http://example.com"' } + @{ Description = 'https with path'; UriString = 'https://example.com/path'; Expected = '"https://example.com/path"' } + @{ Description = 'with query'; UriString = 'https://example.com/search?q=test'; Expected = '"https://example.com/search?q=test"' } + @{ Description = 'file'; UriString = 'file:///c:/temp/file.txt'; Expected = '"file:///c:/temp/file.txt"' } + ) { + param($Description, $UriString, $Expected) + $uri = [Uri]$UriString + $jsonPipeline = $uri | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $uri -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should include ETS properties on Uri' { + $uri = [Uri]'https://example.com' + $uri = Add-Member -InputObject $uri -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $uri | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"https://example.com","MyProp":"test"}' + } + } + + Context 'Enum types' { + It 'Should serialize enum :: as via Pipeline and InputObject' -TestCases @( + @{ EnumType = 'System.DayOfWeek'; Value = 'Sunday'; Expected = '0' } + @{ EnumType = 'System.DayOfWeek'; Value = 'Monday'; Expected = '1' } + @{ EnumType = 'System.DayOfWeek'; Value = 'Saturday'; Expected = '6' } + @{ EnumType = 'System.ConsoleColor'; Value = 'Red'; Expected = '12' } + @{ EnumType = 'System.IO.FileAttributes'; Value = 'ReadOnly'; Expected = '1' } + @{ EnumType = 'System.IO.FileAttributes'; Value = 'Hidden'; Expected = '2' } + ) { + param($EnumType, $Value, $Expected) + $enumValue = [Enum]::Parse($EnumType, $Value) + $jsonPipeline = $enumValue | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $enumValue -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should serialize enum as "" with -EnumsAsStrings' -TestCases @( + @{ EnumType = 'System.DayOfWeek'; Value = 'Sunday'; Expected = 'Sunday' } + @{ EnumType = 'System.DayOfWeek'; Value = 'Monday'; Expected = 'Monday' } + @{ EnumType = 'System.ConsoleColor'; Value = 'Red'; Expected = 'Red' } + ) { + param($EnumType, $Value, $Expected) + $enumValue = [Enum]::Parse($EnumType, $Value) + $json = $enumValue | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly "`"$Expected`"" + } + + It 'Should serialize flags enum correctly via Pipeline and InputObject' { + $flags = [System.IO.FileAttributes]::ReadOnly -bor [System.IO.FileAttributes]::Hidden + $jsonPipeline = $flags | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $flags -Compress + $jsonPipeline | Should -BeExactly '3' + $jsonInputObject | Should -BeExactly '3' + } + + It 'Should serialize flags enum as string with -EnumsAsStrings' { + $flags = [System.IO.FileAttributes]::ReadOnly -bor [System.IO.FileAttributes]::Hidden + $json = $flags | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly '"ReadOnly, Hidden"' + } + + It 'Should include ETS properties on Enum' { + $enum = Add-Member -InputObject ([DayOfWeek]::Monday) -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $enum | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":1,"MyProp":"test"}' + } + } + + Context 'IPAddress type' { + It 'Should serialize IPAddress v4 correctly via InputObject' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $json = ConvertTo-Json -InputObject $ip -Compress + $json | Should -BeExactly '{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952}' + } + + It 'Should serialize IPAddress v4 correctly via Pipeline' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $json = $ip | ConvertTo-Json -Compress + $json | Should -BeExactly '{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952,"IPAddressToString":"192.168.1.1"}' + } + + It 'Should serialize IPAddress v6 correctly via InputObject' { + $ip = [System.Net.IPAddress]::Parse('::1') + $json = ConvertTo-Json -InputObject $ip -Compress + $json | Should -BeExactly '{"AddressFamily":23,"ScopeId":0,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":null}' + } + + It 'Should serialize IPAddress v6 correctly via Pipeline' { + $ip = [System.Net.IPAddress]::Parse('::1') + $json = $ip | ConvertTo-Json -Compress + $json | Should -BeExactly '{"AddressFamily":23,"ScopeId":0,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":null,"IPAddressToString":"::1"}' + } + + It 'Should include ETS properties on IPAddress' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $ip = Add-Member -InputObject $ip -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $ip | ConvertTo-Json -Compress + $json | Should -BeExactly '{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952,"MyProp":"test","IPAddressToString":"192.168.1.1"}' + } + } + + Context 'Scalars as elements of arrays' { + It 'Should serialize array of correctly via Pipeline and InputObject' -TestCases @( + @{ TypeName = 'int'; Values = @(1, 2, 3); Expected = '[1,2,3]' } + @{ TypeName = 'string'; Values = @('a', 'b', 'c'); Expected = '["a","b","c"]' } + @{ TypeName = 'double'; Values = @(1.1, 2.2, 3.3); Expected = '[1.1,2.2,3.3]' } + ) { + param($TypeName, $Values, $Expected) + $jsonPipeline = $Values | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $Values -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + # Note: bool array test uses InputObject only because $true/$false are singletons + # and ETS properties added in other tests would affect Pipeline serialization + It 'Should serialize array of bool correctly via InputObject' { + $bools = @($true, $false, $true) + $json = ConvertTo-Json -InputObject $bools -Compress + $json | Should -BeExactly '[true,false,true]' + } + + It 'Should serialize array of Guid with Extended properties via Pipeline' { + $guids = @( + [Guid]'11111111-1111-1111-1111-111111111111', + [Guid]'22222222-2222-2222-2222-222222222222' + ) + $json = $guids | ConvertTo-Json -Compress + $json | Should -BeExactly '[{"value":"11111111-1111-1111-1111-111111111111","Guid":"11111111-1111-1111-1111-111111111111"},{"value":"22222222-2222-2222-2222-222222222222","Guid":"22222222-2222-2222-2222-222222222222"}]' + } + + It 'Should serialize array of enum correctly via Pipeline and InputObject' { + $enums = @([DayOfWeek]::Monday, [DayOfWeek]::Wednesday, [DayOfWeek]::Friday) + $jsonPipeline = $enums | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $enums -Compress + $jsonPipeline | Should -BeExactly '[1,3,5]' + $jsonInputObject | Should -BeExactly '[1,3,5]' + } + + It 'Should serialize array of enum as strings with -EnumsAsStrings' { + $enums = @([DayOfWeek]::Monday, [DayOfWeek]::Wednesday, [DayOfWeek]::Friday) + $json = $enums | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly '["Monday","Wednesday","Friday"]' + } + + # Note: mixed array test uses InputObject only due to $true singleton issue + It 'Should serialize mixed type array correctly via InputObject' { + $mixed = @(1, 'two', $true, 3.14) + $json = ConvertTo-Json -InputObject $mixed -Compress + $json | Should -BeExactly '[1,"two",true,3.14]' + } + + It 'Should serialize array with null elements correctly' { + $arr = @(1, $null, 'three') + $json = $arr | ConvertTo-Json -Compress + $json | Should -BeExactly '[1,null,"three"]' + } + + It 'Should include ETS properties on array via InputObject' { + $arr = @(1, 2, 3) + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = ConvertTo-Json -InputObject $arr -Compress + $json | Should -BeExactly '{"value":[1,2,3],"MyProp":"test"}' + } + } + + Context 'Scalars as values in hashtables and PSCustomObject' { + It 'Should serialize hashtable with scalar values correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + intVal = 42 + strVal = 'hello' + boolVal = $true + doubleVal = 3.14 + nullVal = $null + } + $expected = '{"intVal":42,"strVal":"hello","boolVal":true,"doubleVal":3.14,"nullVal":null}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with scalar values correctly via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + intVal = 42 + strVal = 'hello' + boolVal = $true + doubleVal = 3.14 + } + $expected = '{"intVal":42,"strVal":"hello","boolVal":true,"doubleVal":3.14}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable with value correctly' -TestCases @( + @{ TypeName = 'DateTime'; Value = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc); Expected = '{"val":"2024-06-15T10:30:00Z"}' } + @{ TypeName = 'Guid'; Value = [Guid]'12345678-1234-1234-1234-123456789abc'; Expected = '{"val":"12345678-1234-1234-1234-123456789abc"}' } + @{ TypeName = 'Enum'; Value = [DayOfWeek]::Monday; Expected = '{"val":1}' } + @{ TypeName = 'Uri'; Value = [Uri]'https://example.com'; Expected = '{"val":"https://example.com"}' } + @{ TypeName = 'BigInteger'; Value = 18446744073709551615n; Expected = '{"val":18446744073709551615}' } + ) { + param($TypeName, $Value, $Expected) + $hash = @{ val = $Value } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should serialize hashtable with enum as string correctly' { + $hash = @{ day = [DayOfWeek]::Monday } + $json = $hash | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly '{"day":"Monday"}' + } + + It 'Should include ETS properties on hashtable via InputObject' { + $hash = @{ a = 1 } + $hash = Add-Member -InputObject $hash -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = ConvertTo-Json -InputObject $hash -Compress + $json | Should -BeExactly '{"a":1,"MyProp":"test"}' + } + } + + #endregion Comprehensive Scalar Type Tests (Phase 1) } From 438254412760ff24cdfebeed09bfeb3a24882866 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Sat, 7 Feb 2026 02:41:03 +0900 Subject: [PATCH 177/255] Add comprehensive array and dictionary tests for `ConvertTo-Json` (#26742) --- .../ConvertTo-Json.Tests.ps1 | 457 ++++++++++++++++++ 1 file changed, 457 insertions(+) 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 4ac0818faf4..d3686953336 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 @@ -585,4 +585,461 @@ Describe 'ConvertTo-Json' -tags "CI" { } #endregion Comprehensive Scalar Type Tests (Phase 1) + + #region Comprehensive Array and Dictionary Tests (Phase 2) + # Test coverage for ConvertTo-Json array and dictionary serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, nested structures + + Context 'Array basic serialization' { + It 'Should serialize empty array correctly via Pipeline and InputObject' { + $arr = @() + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[]' + $jsonInputObject | Should -BeExactly '[]' + } + + It 'Should serialize single element array correctly via Pipeline and InputObject' { + $arr = @(42) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[42]' + $jsonInputObject | Should -BeExactly '[42]' + } + + It 'Should serialize multi-element array correctly via Pipeline and InputObject' { + $arr = @(1, 2, 3, 4, 5) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,2,3,4,5]' + $jsonInputObject | Should -BeExactly '[1,2,3,4,5]' + } + + It 'Should serialize string array correctly via Pipeline and InputObject' { + $arr = @('apple', 'banana', 'cherry') + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '["apple","banana","cherry"]' + $jsonInputObject | Should -BeExactly '["apple","banana","cherry"]' + } + + It 'Should serialize typed array correctly via Pipeline and InputObject' { + [int[]]$arr = @(10, 20, 30) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[10,20,30]' + $jsonInputObject | Should -BeExactly '[10,20,30]' + } + + It 'Should serialize array with single null element correctly via Pipeline and InputObject' { + $arr = @($null) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[null]' + $jsonInputObject | Should -BeExactly '[null]' + } + + It 'Should serialize array with multiple null elements correctly via Pipeline and InputObject' { + $arr = @($null, $null, $null) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[null,null,null]' + $jsonInputObject | Should -BeExactly '[null,null,null]' + } + } + + Context 'Nested arrays' { + It 'Should serialize 2D array correctly via Pipeline and InputObject' { + $arr = @(@(1, 2), @(3, 4)) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[1,2],[3,4]]' + $jsonInputObject | Should -BeExactly '[[1,2],[3,4]]' + } + + It 'Should serialize 3D array correctly via Pipeline and InputObject' { + $arr = @(@(@(1, 2), @(3, 4)), @(@(5, 6), @(7, 8))) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[[1,2],[3,4]],[[5,6],[7,8]]]' + $jsonInputObject | Should -BeExactly '[[[1,2],[3,4]],[[5,6],[7,8]]]' + } + + It 'Should serialize jagged array correctly via Pipeline and InputObject' { + $arr = @(@(1), @(2, 3), @(4, 5, 6)) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[1],[2,3],[4,5,6]]' + $jsonInputObject | Should -BeExactly '[[1],[2,3],[4,5,6]]' + } + + It 'Should serialize array containing empty arrays correctly via Pipeline and InputObject' { + $arr = @(@(), @(1), @()) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[],[1],[]]' + $jsonInputObject | Should -BeExactly '[[],[1],[]]' + } + + It 'Should serialize deeply nested array with Depth limit using ToString via Pipeline and InputObject' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $arr = ,(,(,(,($ip)))) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 2 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 2 + $jsonPipeline | Should -BeExactly '[[["192.168.1.1"]]]' + $jsonInputObject | Should -BeExactly '[[["192.168.1.1"]]]' + } + + It 'Should serialize deeply nested array with sufficient Depth as full object via Pipeline and InputObject' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $arr = ,(,(,(,($ip)))) + $expected = '[[[[{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952}]]]]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Array with mixed content types' { + It 'Should serialize array with mixed scalars correctly via Pipeline and InputObject' { + $arr = @(1, 'two', 3.14, $true, $null) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,"two",3.14,true,null]' + $jsonInputObject | Should -BeExactly '[1,"two",3.14,true,null]' + } + + It 'Should serialize array with nested array and scalars correctly via Pipeline and InputObject' { + $arr = @(1, @(2, 3), 4) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,[2,3],4]' + $jsonInputObject | Should -BeExactly '[1,[2,3],4]' + } + + It 'Should serialize array with PSCustomObject elements correctly via Pipeline and InputObject' { + $arr = @([PSCustomObject]@{x = 1}, [PSCustomObject]@{y = 2}) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[{"x":1},{"y":2}]' + $jsonInputObject | Should -BeExactly '[{"x":1},{"y":2}]' + } + + It 'Should serialize array with DateTime elements correctly via Pipeline and InputObject' { + $date1 = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) + $date2 = [DateTime]::new(2024, 12, 25, 0, 0, 0, [DateTimeKind]::Utc) + $arr = @($date1, $date2) + $expected = '["2024-06-15T10:30:00Z","2024-12-25T00:00:00Z"]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array with Guid elements correctly via Pipeline and InputObject' { + $guid1 = [Guid]'12345678-1234-1234-1234-123456789abc' + $guid2 = [Guid]'87654321-4321-4321-4321-cba987654321' + $arr = @($guid1, $guid2) + $expected = '["12345678-1234-1234-1234-123456789abc","87654321-4321-4321-4321-cba987654321"]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array with enum elements correctly via Pipeline and InputObject' { + $arr = @([DayOfWeek]::Monday, [DayOfWeek]::Friday) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,5]' + $jsonInputObject | Should -BeExactly '[1,5]' + } + + It 'Should serialize array with enum as string correctly via Pipeline and InputObject' { + $arr = @([DayOfWeek]::Monday, [DayOfWeek]::Friday) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -EnumsAsStrings + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -EnumsAsStrings + $jsonPipeline | Should -BeExactly '["Monday","Friday"]' + $jsonInputObject | Should -BeExactly '["Monday","Friday"]' + } + } + + Context 'Array ETS properties' { + It 'Should include ETS properties on array via Pipeline and InputObject' { + $arr = @(1, 2, 3) + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name ArrayName -Value 'MyArray' -PassThru + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '{"value":[1,2,3],"ArrayName":"MyArray"}' + $jsonInputObject | Should -BeExactly '{"value":[1,2,3],"ArrayName":"MyArray"}' + } + + It 'Should include multiple ETS properties on array via Pipeline and InputObject' { + $arr = @('a', 'b') + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name Prop1 -Value 'val1' -PassThru + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name Prop2 -Value 'val2' -PassThru + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '{"value":["a","b"],"Prop1":"val1","Prop2":"val2"}' + $jsonInputObject | Should -BeExactly '{"value":["a","b"],"Prop1":"val1","Prop2":"val2"}' + } + } + + Context 'Hashtable basic serialization' { + It 'Should serialize empty hashtable correctly via Pipeline and InputObject' { + $hash = @{} + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{}' + $jsonInputObject | Should -BeExactly '{}' + } + + It 'Should serialize single key hashtable correctly via Pipeline and InputObject' { + $hash = @{ key = 'value' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"key":"value"}' + $jsonInputObject | Should -BeExactly '{"key":"value"}' + } + + It 'Should serialize hashtable with null value correctly via Pipeline and InputObject' { + $hash = @{ nullKey = $null } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"nullKey":null}' + $jsonInputObject | Should -BeExactly '{"nullKey":null}' + } + + It 'Should serialize hashtable with various scalar types correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + intKey = 42 + strKey = 'hello' + boolKey = $true + doubleKey = 3.14 + } + $expected = '{"intKey":42,"strKey":"hello","boolKey":true,"doubleKey":3.14}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'OrderedDictionary serialization' { + It 'Should preserve order in OrderedDictionary via Pipeline and InputObject' { + $ordered = [ordered]@{ + z = 1 + a = 2 + m = 3 + } + $expected = '{"z":1,"a":2,"m":3}' + $jsonPipeline = $ordered | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $ordered -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize large OrderedDictionary preserving order via Pipeline and InputObject' { + $ordered = [ordered]@{} + 1..5 | ForEach-Object { $ordered["key$_"] = $_ } + $expected = '{"key1":1,"key2":2,"key3":3,"key4":4,"key5":5}' + $jsonPipeline = $ordered | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $ordered -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Nested dictionaries' { + It 'Should serialize nested hashtable correctly via Pipeline and InputObject' { + $hash = @{ + outer = @{ + inner = 'value' + } + } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"outer":{"inner":"value"}}' + $jsonInputObject | Should -BeExactly '{"outer":{"inner":"value"}}' + } + + It 'Should serialize deeply nested hashtable correctly via Pipeline and InputObject' { + $hash = @{ + level1 = @{ + level2 = @{ + level3 = 'deep' + } + } + } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"level1":{"level2":{"level3":"deep"}}}' + $jsonInputObject | Should -BeExactly '{"level1":{"level2":{"level3":"deep"}}}' + } + + It 'Should serialize nested hashtable with Depth limit via Pipeline and InputObject' { + $hash = @{ + level1 = @{ + level2 = @{ + level3 = 'deep' + } + } + } + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 1 + $jsonPipeline | Should -BeExactly '{"level1":{"level2":"System.Collections.Hashtable"}}' + $jsonInputObject | Should -BeExactly '{"level1":{"level2":"System.Collections.Hashtable"}}' + } + + It 'Should serialize hashtable with array value correctly via Pipeline and InputObject' { + $hash = @{ arr = @(1, 2, 3) } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"arr":[1,2,3]}' + $jsonInputObject | Should -BeExactly '{"arr":[1,2,3]}' + } + + It 'Should serialize hashtable with nested array of hashtables correctly via Pipeline and InputObject' { + $hash = @{ + items = @( + @{ id = 1 }, + @{ id = 2 } + ) + } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"items":[{"id":1},{"id":2}]}' + $jsonInputObject | Should -BeExactly '{"items":[{"id":1},{"id":2}]}' + } + } + + Context 'Dictionary key types' { + It 'Should serialize hashtable with string keys correctly via Pipeline and InputObject' { + $hash = @{ 'string-key' = 'value' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"string-key":"value"}' + $jsonInputObject | Should -BeExactly '{"string-key":"value"}' + } + + It 'Should serialize hashtable with special character keys correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + 'key with space' = 1 + 'key-with-dash' = 2 + 'key_with_underscore' = 3 + } + $expected = '{"key with space":1,"key-with-dash":2,"key_with_underscore":3}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable with unicode keys correctly via Pipeline and InputObject' { + $hash = @{ "`u{65E5}`u{672C}`u{8A9E}" = 'Japanese' } + $expected = "{`"`u{65E5}`u{672C}`u{8A9E}`":`"Japanese`"}" + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable with empty string key correctly via Pipeline and InputObject' { + $hash = @{ '' = 'empty key' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"":"empty key"}' + $jsonInputObject | Should -BeExactly '{"":"empty key"}' + } + } + + Context 'Dictionary with complex values' { + It 'Should serialize hashtable with DateTime value correctly via Pipeline and InputObject' { + $hash = @{ date = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"date":"2024-06-15T10:30:00Z"}' + $jsonInputObject | Should -BeExactly '{"date":"2024-06-15T10:30:00Z"}' + } + + It 'Should serialize hashtable with Guid value correctly via Pipeline and InputObject' { + $hash = @{ guid = [Guid]'12345678-1234-1234-1234-123456789abc' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"guid":"12345678-1234-1234-1234-123456789abc"}' + $jsonInputObject | Should -BeExactly '{"guid":"12345678-1234-1234-1234-123456789abc"}' + } + + It 'Should serialize hashtable with enum value correctly via Pipeline and InputObject' { + $hash = @{ day = [DayOfWeek]::Monday } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"day":1}' + $jsonInputObject | Should -BeExactly '{"day":1}' + } + + It 'Should serialize hashtable with enum as string correctly via Pipeline and InputObject' { + $hash = @{ day = [DayOfWeek]::Monday } + $jsonPipeline = $hash | ConvertTo-Json -Compress -EnumsAsStrings + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -EnumsAsStrings + $jsonPipeline | Should -BeExactly '{"day":"Monday"}' + $jsonInputObject | Should -BeExactly '{"day":"Monday"}' + } + + It 'Should serialize hashtable with PSCustomObject value correctly via Pipeline and InputObject' { + $hash = @{ obj = [PSCustomObject]@{ prop = 'value' } } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"obj":{"prop":"value"}}' + $jsonInputObject | Should -BeExactly '{"obj":{"prop":"value"}}' + } + } + + Context 'Dictionary ETS properties' { + It 'Should include ETS properties on hashtable via Pipeline and InputObject' { + $hash = @{ a = 1 } + $hash = Add-Member -InputObject $hash -MemberType NoteProperty -Name ETSProp -Value 'ets' -PassThru + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + $jsonInputObject | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + } + + It 'Should include ETS properties on OrderedDictionary via Pipeline and InputObject' { + $ordered = [ordered]@{ a = 1 } + $ordered = Add-Member -InputObject $ordered -MemberType NoteProperty -Name ETSProp -Value 'ets' -PassThru + $jsonPipeline = $ordered | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $ordered -Compress + $jsonPipeline | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + $jsonInputObject | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + } + } + + Context 'Generic Dictionary types' { + It 'Should serialize Generic Dictionary correctly via Pipeline and InputObject' { + $dict = [System.Collections.Generic.Dictionary[string,int]]::new() + $dict['one'] = 1 + $dict['two'] = 2 + $jsonPipeline = $dict | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dict -Compress + $jsonPipeline | Should -Match '"one":1' + $jsonPipeline | Should -Match '"two":2' + $jsonInputObject | Should -Match '"one":1' + $jsonInputObject | Should -Match '"two":2' + } + + It 'Should serialize SortedDictionary correctly via Pipeline and InputObject' { + $dict = [System.Collections.Generic.SortedDictionary[string,int]]::new() + $dict['b'] = 2 + $dict['a'] = 1 + $dict['c'] = 3 + $jsonPipeline = $dict | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dict -Compress + $jsonPipeline | Should -BeExactly '{"a":1,"b":2,"c":3}' + $jsonInputObject | Should -BeExactly '{"a":1,"b":2,"c":3}' + } + } + + #endregion Comprehensive Array and Dictionary Tests (Phase 2) } From e58d395dbb84f783dc7fbc9dd74e444e8a455de3 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 6 Feb 2026 18:11:18 -0500 Subject: [PATCH 178/255] Create es-metadata (#26759) Co-authored-by: MerlinBot --- es-metadata.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 es-metadata.yml diff --git a/es-metadata.yml b/es-metadata.yml new file mode 100644 index 00000000000..24da115c114 --- /dev/null +++ b/es-metadata.yml @@ -0,0 +1,12 @@ +schemaVersion: 1.0.0 +providers: +- provider: InventoryAsCode + version: 1.0.0 + metadata: + isProduction: true + accountableOwners: + service: cef1de07-99d6-45df-b907-77d0066032ec + routing: + defaultAreaPath: + org: msazure + path: One\MGMT\Compute\Powershell\Powershell\Powershell Core\pwsh From 012529a967f6f4a0b2e874c2d98dc6efe79506cb Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 9 Feb 2026 13:13:17 -0500 Subject: [PATCH 179/255] Update Microsoft.PowerShell.PSResourceGet version to 1.2.0-rc3 (#26753) --- src/Modules/PSGalleryModules.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index cbc47ef200c..7008fe6a08d 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -13,7 +13,7 @@ - + From 664ac75f4fe8ef4008d40e11965b0df90180f022 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Tue, 10 Feb 2026 09:43:02 +0900 Subject: [PATCH 180/255] Add comprehensive depth and multilevel composition tests for `ConvertTo-Json` (#26744) --- .../ConvertTo-Json.Tests.ps1 | 452 ++++++++++++++++++ 1 file changed, 452 insertions(+) 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 d3686953336..4fe4b0d59b6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 @@ -1042,4 +1042,456 @@ Describe 'ConvertTo-Json' -tags "CI" { } #endregion Comprehensive Array and Dictionary Tests (Phase 2) + + #region Comprehensive Depth Truncation and Multilevel Composition Tests (Phase 4) + # Test coverage for ConvertTo-Json depth truncation and complex nested structures + # Covers: -Depth parameter behavior, multilevel type compositions + + Context 'Depth parameter basic behavior' { + It 'Should use default depth of 2 via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ + L1 = [PSCustomObject]@{ + L2 = [PSCustomObject]@{ + L3 = 'deep' + } + } + } + } + $expected = '{"L0":{"L1":{"L2":"@{L3=deep}"}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate at Depth 0 via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ L1 = 1 } + } + $expected = '{"L0":"@{L1=1}"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate at Depth 1 via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ + L1 = [PSCustomObject]@{ + L2 = 'deep' + } + } + } + $expected = '{"L0":{"L1":"@{L2=deep}"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize fully with sufficient Depth via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ + L1 = [PSCustomObject]@{ + L2 = [PSCustomObject]@{ + L3 = 'very deep' + } + } + } + } + $expected = '{"L0":{"L1":{"L2":{"L3":"very deep"}}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should handle Depth 100 for deeply nested structures via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ L0 = [PSCustomObject]@{ L1 = [PSCustomObject]@{ L2 = [PSCustomObject]@{ L3 = [PSCustomObject]@{ L4 = 'deep' } } } } } + $expected = '{"L0":{"L1":{"L2":{"L3":{"L4":"deep"}}}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 100 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 100 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should throw on Depth 101 exceeding maximum via Pipeline and InputObject' { + { [PSCustomObject]@{ L0 = 1 } | ConvertTo-Json -Depth 101 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ConvertToJsonCommand' + { ConvertTo-Json -InputObject ([PSCustomObject]@{ L0 = 1 }) -Depth 101 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ConvertToJsonCommand' + } + } + + Context 'Depth truncation with arrays' { + It 'Should truncate nested array at Depth limit via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Arr = ,(,(1, 2, 3)) + } + $expected = '{"Arr":["System.Object[]"]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested array fully with sufficient Depth via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Arr = ,(,(1, 2, 3)) + } + $expected = '{"Arr":[[[1,2,3]]]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate array of objects at Depth limit via Pipeline and InputObject' { + $arr = @( + [PSCustomObject]@{ Inner = [PSCustomObject]@{ Value = 1 } } + ) + $expected = '[{"Inner":"@{Value=1}"}]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Depth truncation with hashtables' { + It 'Should truncate nested hashtable at Depth limit via Pipeline and InputObject' { + $hash = @{ + L0 = @{ + L1 = @{ + L2 = 'deep' + } + } + } + $expected = '{"L0":{"L1":"System.Collections.Hashtable"}}' + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested hashtable fully with sufficient Depth via Pipeline and InputObject' { + $hash = @{ + L0 = @{ + L1 = @{ + L2 = 'deep' + } + } + } + $expected = '{"L0":{"L1":{"L2":"deep"}}}' + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Depth truncation string representation' { + It 'Should convert PSCustomObject to @{...} string when truncated via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = [PSCustomObject]@{ A = 1; B = 2 } + } + $expected = '{"Child":"@{A=1; B=2}"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should convert Hashtable to type name when truncated via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = @{ Key = 'Value' } + } + $expected = '{"Child":"System.Collections.Hashtable"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should convert Array to space-separated string when truncated via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = @(1, 2, 3) + } + $expected = '{"Child":"1 2 3"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: Array containing Dictionary' { + It 'Should serialize array of hashtables correctly via Pipeline and InputObject' { + $arr = @(@{ a = 1 }, @{ b = 2 }, @{ c = 3 }) + $expected = '[{"a":1},{"b":2},{"c":3}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array of ordered dictionaries correctly via Pipeline and InputObject' { + $arr = @( + [ordered]@{ x = 1; y = 2 }, + [ordered]@{ x = 3; y = 4 } + ) + $expected = '[{"x":1,"y":2},{"x":3,"y":4}]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested array of hashtables correctly via Pipeline and InputObject' { + $arr = @( + @{ + Items = @( + @{ Value = 1 }, + @{ Value = 2 } + ) + } + ) + $expected = '[{"Items":[{"Value":1},{"Value":2}]}]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: Dictionary containing Array' { + It 'Should serialize dictionary with array values correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + numbers = @(1, 2, 3) + strings = @('a', 'b', 'c') + } + $expected = '{"numbers":[1,2,3],"strings":["a","b","c"]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with nested array values correctly via Pipeline and InputObject' { + $hash = @{ + matrix = @(@(1, 2), @(3, 4)) + } + $expected = '{"matrix":[[1,2],[3,4]]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with empty array value correctly via Pipeline and InputObject' { + $hash = @{ empty = @() } + $expected = '{"empty":[]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with array of dictionaries correctly via Pipeline and InputObject' { + $hash = @{ + Items = @( + @{ X = 1 }, + @{ X = 2 } + ) + } + $expected = '{"Items":[{"X":1},{"X":2}]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: PSCustomObject with mixed types' { + It 'Should serialize PSCustomObject with array and hashtable properties via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + List = @(1, 2, 3) + Config = @{ Key = 'Value' } + Name = 'Test' + } + $expected = '{"List":[1,2,3],"Config":{"Key":"Value"},"Name":"Test"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with nested PSCustomObject and array via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = [PSCustomObject]@{ + Items = @(1, 2, 3) + } + } + $expected = '{"Child":{"Items":[1,2,3]}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array of PSCustomObject with mixed properties via Pipeline and InputObject' { + $arr = @( + [PSCustomObject]@{ Type = 'A'; Data = @(1, 2) }, + [PSCustomObject]@{ Type = 'B'; Data = @{ Key = 'Val' } } + ) + $expected = '[{"Type":"A","Data":[1,2]},{"Type":"B","Data":{"Key":"Val"}}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: PowerShell class in complex structures' { + BeforeAll { + class ItemClass { + [int]$Id + [string]$Name + } + + class ContainerClass { + [string]$Type + [ItemClass]$Item + } + } + + It 'Should serialize array of PowerShell class correctly via Pipeline and InputObject' { + $arr = @( + [ItemClass]@{ Id = 1; Name = 'First' }, + [ItemClass]@{ Id = 2; Name = 'Second' } + ) + $expected = '[{"Id":1,"Name":"First"},{"Id":2,"Name":"Second"}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable containing PowerShell class correctly via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Test' } + $hash = @{ Item = $item } + $expected = '{"Item":{"Id":1,"Name":"Test"}}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested PowerShell classes correctly via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Inner' } + $container = [ContainerClass]@{ Type = 'Outer'; Item = $item } + $expected = '{"Type":"Outer","Item":{"Id":1,"Name":"Inner"}}' + $jsonPipeline = $container | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $container -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject containing PowerShell class correctly via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Test' } + $obj = [PSCustomObject]@{ + Label = 'Container' + Content = $item + } + $expected = '{"Label":"Container","Content":{"Id":1,"Name":"Test"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate nested PowerShell class at Depth limit via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Test' } + $container = [ContainerClass]@{ Type = 'Outer'; Item = $item } + $itemString = $item.ToString() + $expected = "{`"Type`":`"Outer`",`"Item`":`"$itemString`"}" + $jsonPipeline = $container | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $container -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Complex multilevel compositions' { + It 'Should serialize 3-level mixed composition correctly via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Users = @( + [PSCustomObject]@{ + Name = 'Alice' + Roles = @('Admin', 'User') + }, + [PSCustomObject]@{ + Name = 'Bob' + Roles = @('User') + } + ) + } + $expected = '{"Users":[{"Name":"Alice","Roles":["Admin","User"]},{"Name":"Bob","Roles":["User"]}]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with nested mixed types correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + Meta = [PSCustomObject]@{ Version = '1.0' } + Data = @( + ([ordered]@{ Key = 'A'; Values = @(1, 2) }), + ([ordered]@{ Key = 'B'; Values = @(3, 4) }) + ) + } + $expected = '{"Meta":{"Version":"1.0"},"Data":[{"Key":"A","Values":[1,2]},{"Key":"B","Values":[3,4]}]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should handle deeply nested mixed types with sufficient Depth via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = @{ + L1 = [PSCustomObject]@{ + L2 = @( + [PSCustomObject]@{ L3 = 'deep' } + ) + } + } + } + $expected = '{"L0":{"L1":{"L2":[{"L3":"deep"}]}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate deeply nested mixed types at Depth limit via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = @{ + L1 = [PSCustomObject]@{ + L2 = @( + [PSCustomObject]@{ L3 = 'deep' } + ) + } + } + } + $expected = '{"L0":{"L1":{"L2":""}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 2 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 2 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + #endregion Comprehensive Depth Truncation and Multilevel Composition Tests (Phase 4) } From 42371b881c8189e871761e11d584e35acb139f64 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:10:50 -0800 Subject: [PATCH 181/255] Add `SubjectAlternativeName` property to the `Signature` object returned from `Get-AuthenticodeSignature` (#26252) --- .../security/MshSignature.cs | 37 +++++++ .../security/SecuritySupport.cs | 1 + .../engine/Security/FileSignature.Tests.ps1 | 101 ++++++++++++++++++ 3 files changed, 139 insertions(+) diff --git a/src/System.Management.Automation/security/MshSignature.cs b/src/System.Management.Automation/security/MshSignature.cs index fd8dd4f67ef..7cbaf98d3a5 100644 --- a/src/System.Management.Automation/security/MshSignature.cs +++ b/src/System.Management.Automation/security/MshSignature.cs @@ -185,6 +185,11 @@ public string Path /// public bool IsOSBinary { get; internal set; } + /// + /// Gets the Subject Alternative Name from the signer certificate. + /// + public string[] SubjectAlternativeName { get; private set; } + /// /// Constructor for class Signature /// @@ -277,6 +282,9 @@ private void Init(string filePath, _statusMessage = GetSignatureStatusMessage(isc, error, filePath); + + // Extract Subject Alternative Name from the signer certificate + SubjectAlternativeName = GetSubjectAlternativeName(signer); } private static SignatureStatus GetSignatureStatusFromWin32Error(DWORD error) @@ -389,5 +397,34 @@ private static string GetSignatureStatusMessage(SignatureStatus status, return message; } + + /// + /// Extracts the Subject Alternative Name from the certificate. + /// + /// The certificate to extract SAN from. + /// Array of SAN entries or null if not found. + private static string[] GetSubjectAlternativeName(X509Certificate2 certificate) + { + if (certificate == null) + { + return null; + } + + foreach (X509Extension extension in certificate.Extensions) + { + if (extension.Oid != null && extension.Oid.Value == CertificateFilterInfo.SubjectAlternativeNameOid) + { + string formatted = extension.Format(multiLine: true); + if (string.IsNullOrEmpty(formatted)) + { + return null; + } + + return formatted.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries); + } + } + + return null; + } } } diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index e6a6f10416b..12e03d85174 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -815,6 +815,7 @@ internal DateTime Expiring // The OID arc 1.3.6.1.4.1.311.80 is assigned to PowerShell. If we need // new OIDs, we can assign them under this branch. internal const string DocumentEncryptionOid = "1.3.6.1.4.1.311.80.1"; + internal const string SubjectAlternativeNameOid = "2.5.29.17"; } } diff --git a/test/powershell/engine/Security/FileSignature.Tests.ps1 b/test/powershell/engine/Security/FileSignature.Tests.ps1 index 9dd26f98aed..f2815fad4ff 100644 --- a/test/powershell/engine/Security/FileSignature.Tests.ps1 +++ b/test/powershell/engine/Security/FileSignature.Tests.ps1 @@ -18,6 +18,9 @@ Describe "Windows platform file signatures" -Tags 'Feature' { $signature | Should -Not -BeNullOrEmpty $signature.Status | Should -BeExactly 'Valid' $signature.SignatureType | Should -BeExactly 'Catalog' + + # Verify that SubjectAlternativeName property exists + $signature.PSObject.Properties.Name | Should -Contain 'SubjectAlternativeName' } } @@ -186,4 +189,102 @@ Describe "Windows file content signatures" -Tags @('Feature', 'RequireAdminOnWin $actual.SignerCertificate.Thumbprint | Should -Be $certificate.Thumbprint $actual.Status | Should -Be 'Valid' } + + It "Verifies SubjectAlternativeName is populated for certificate with SAN" { + $session = New-PSSession -UseWindowsPowerShell + try { + $sanThumbprint = Invoke-Command -Session $session -ScriptBlock { + $testPrefix = 'SelfSignedTestSAN' + + $enhancedKeyUsage = [Security.Cryptography.OidCollection]::new() + $null = $enhancedKeyUsage.Add('1.3.6.1.5.5.7.3.3') + + $caParams = @{ + Extension = @( + [Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true, $false, 0, $true), + [Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new('KeyCertSign', $false), + [Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($enhancedKeyUsage, $false) + ) + CertStoreLocation = 'Cert:\CurrentUser\My' + NotAfter = (Get-Date).AddDays(1) + Type = 'Custom' + } + $sanCA = PKI\New-SelfSignedCertificate @caParams -Subject "CN=$testPrefix-CA" + + $rootStore = Get-Item -Path Cert:\LocalMachine\Root + $rootStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + try { + $rootStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sanCA.RawData)) + } finally { + $rootStore.Close() + } + + $certParams = @{ + CertStoreLocation = 'Cert:\CurrentUser\My' + KeyUsage = 'DigitalSignature' + TextExtension = @( + "2.5.29.37={text}1.3.6.1.5.5.7.3.3", + "2.5.29.19={text}", + "2.5.29.17={text}DNS=test.example.com&DNS=*.example.com" + ) + Type = 'Custom' + } + $sanCert = PKI\New-SelfSignedCertificate @certParams -Subject "CN=$testPrefix-Signed" -Signer $sanCA + + $publisherStore = Get-Item -Path Cert:\LocalMachine\TrustedPublisher + $publisherStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + try { + $publisherStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sanCert.RawData)) + } finally { + $publisherStore.Close() + } + + $sanCA | Remove-Item + $sanCA.Thumbprint, $sanCert.Thumbprint + } + } finally { + $session | Remove-PSSession + } + + $sanCARootThumbprint = $sanThumbprint[0] + $sanCertThumbprint = $sanThumbprint[1] + $sanCertificate = Get-Item -Path Cert:\CurrentUser\My\$sanCertThumbprint + + try { + Set-Content -Path testdrive:\test.ps1 -Value 'Write-Output "Test SAN"' -Encoding UTF8NoBOM + + $scriptPath = Join-Path $TestDrive test.ps1 + $status = Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $sanCertificate + $status.Status | Should -Be 'Valid' + + $actual = Get-AuthenticodeSignature -FilePath $scriptPath + $actual.SubjectAlternativeName | Should -Not -BeNullOrEmpty + ,$actual.SubjectAlternativeName | Should -BeOfType [string[]] + $actual.SubjectAlternativeName.Count | Should -Be 2 + $actual.SubjectAlternativeName[0] | Should -BeExactly 'DNS Name=test.example.com' + $actual.SubjectAlternativeName[1] | Should -BeExactly 'DNS Name=*.example.com' + } finally { + Remove-Item -Path "Cert:\LocalMachine\Root\$sanCARootThumbprint" -Force -ErrorAction Ignore + Remove-Item -Path "Cert:\LocalMachine\TrustedPublisher\$sanCertThumbprint" -Force -ErrorAction Ignore + Remove-Item -Path "Cert:\CurrentUser\My\$sanCertThumbprint" -Force -ErrorAction Ignore + } + } + + It "Verifies SubjectAlternativeName is null when certificate has no SAN" { + Set-Content -Path testdrive:\test.ps1 -Value 'Write-Output "Test No SAN"' -Encoding UTF8NoBOM + + $scriptPath = Join-Path $TestDrive test.ps1 + $status = Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $certificate + $status.Status | Should -Be 'Valid' + + $actual = Get-AuthenticodeSignature -FilePath $scriptPath + $actual.SignerCertificate.Thumbprint | Should -Be $certificate.Thumbprint + $actual.Status | Should -Be 'Valid' + + # Verify that SubjectAlternativeName property exists + $actual.PSObject.Properties.Name | Should -Contain 'SubjectAlternativeName' + + # Verify the content is null when certificate has no SAN extension + $actual.SubjectAlternativeName | Should -BeNullOrEmpty + } } From 9f9f8b93a39f1051f854561532d9651a2e07d269 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:27:36 +0000 Subject: [PATCH 182/255] Bump github/codeql-action from 4.32.1 to 4.32.2 (#26755) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index b56d2d6635c..c5a0b9ff985 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v3.29.5 + uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v3.29.5 + uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d1d974f8efa..a5d05091555 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v3.29.5 + uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3.29.5 with: sarif_file: results.sarif From 439c42f232fd3e6793425be65aabc942f044cdce Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Tue, 10 Feb 2026 08:02:44 -0800 Subject: [PATCH 183/255] Update outdated package references (#26771) --- .../Microsoft.PowerShell.Commands.Utility.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 6 +++--- .../ResultsComparer/ResultsComparer.csproj | 2 +- tools/cgmanifest.json | 16 ++++++++-------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 5ee999c2d9c..eed0722b4d5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 161170355e6..0f827aab553 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -24,9 +24,9 @@ - - - + + + diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index c4831924845..5cbc66081a3 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -10,6 +10,6 @@ - + diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json index 1001370ade7..2334f070852 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest.json @@ -25,7 +25,7 @@ "Type": "nuget", "Nuget": { "Name": "Humanizer.Core", - "Version": "3.0.1" + "Version": "2.14.1" } }, "DevelopmentDependency": false @@ -35,7 +35,7 @@ "Type": "nuget", "Nuget": { "Name": "Json.More.Net", - "Version": "2.2.0" + "Version": "2.1.1" } }, "DevelopmentDependency": false @@ -45,7 +45,7 @@ "Type": "nuget", "Nuget": { "Name": "JsonPointer.Net", - "Version": "6.0.1" + "Version": "5.3.1" } }, "DevelopmentDependency": false @@ -65,7 +65,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.44.0" + "Version": "0.45.0" } }, "DevelopmentDependency": false @@ -665,7 +665,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Http", - "Version": "8.1.2" + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -675,7 +675,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.NetFramingBase", - "Version": "8.1.2" + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -685,7 +685,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.NetTcp", - "Version": "8.1.2" + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -695,7 +695,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Primitives", - "Version": "8.1.2" + "Version": "10.0.652802" } }, "DevelopmentDependency": false From 2e26e4b69076a22173ccf8d0ebd6f2c1d590578a Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 12 Feb 2026 08:50:16 -0800 Subject: [PATCH 184/255] Update the `Update-Help` tests to use `-Force` to remove read-only files (#26780) --- test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 index 593a2ee89d7..e871a8d1603 100644 --- a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 @@ -215,7 +215,7 @@ function RunUpdateHelpTests It ('Validate Update-Help for module ''{0}'' in {1}' -F $moduleName, [PSCustomObject] $updateScope) -Skip:(!(Test-CanWriteToPsHome) -and $userscope -eq $false) { # Delete the whole help directory - Remove-Item ($moduleHelpPath) -Recurse + Remove-Item ($moduleHelpPath) -Recurse -Force -ErrorAction SilentlyContinue [hashtable] $UICultureParam = $(if ((Get-UICulture).Name -ne $myUICulture) { @{ UICulture = $myUICulture } } else { @{} }) [hashtable] $sourcePathParam = $(if ($useSourcePath) { @{ SourcePath = Join-Path $PSScriptRoot assets } } else { @{} }) From 4db414ca3345dd78232096233226584a5fda9895 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:45:17 +0000 Subject: [PATCH 185/255] Add GitHub Actions annotations for Pester test failures (#26789) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk --- .../process-pester-results.ps1 | 56 +++++++++++++++++ build.psm1 | 63 +++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/.github/actions/test/process-pester-results/process-pester-results.ps1 b/.github/actions/test/process-pester-results/process-pester-results.ps1 index 523de3bebaa..5804bec9a94 100644 --- a/.github/actions/test/process-pester-results/process-pester-results.ps1 +++ b/.github/actions/test/process-pester-results/process-pester-results.ps1 @@ -24,6 +24,7 @@ $testIgnoredCount = 0 $testSkippedCount = 0 $testInvalidCount = 0 +# Process test results and generate annotations for failures Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { $results = [xml] (get-content $_.FullName) @@ -35,6 +36,61 @@ Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { $testIgnoredCount += [int]$results.'test-results'.ignored $testSkippedCount += [int]$results.'test-results'.skipped $testInvalidCount += [int]$results.'test-results'.invalid + + # Generate GitHub Actions annotations for test failures + # Select failed test cases + if ("System.Xml.XmlDocumentXPathExtensions" -as [Type]) { + $failures = [System.Xml.XmlDocumentXPathExtensions]::SelectNodes($results.'test-results', './/test-case[@result = "Failure"]') + } + else { + $failures = $results.SelectNodes('.//test-case[@result = "Failure"]') + } + + foreach ($testfail in $failures) { + $description = $testfail.description + $testName = $testfail.name + $message = $testfail.failure.message + $stack_trace = $testfail.failure.'stack-trace' + + # Parse stack trace to get file and line info + $fileInfo = Get-PesterFailureFileInfo -StackTraceString $stack_trace + + if ($fileInfo.File) { + # Convert absolute path to relative path for GitHub Actions + $filePath = $fileInfo.File + + # GitHub Actions expects paths relative to the workspace root + if ($env:GITHUB_WORKSPACE) { + $workspacePath = $env:GITHUB_WORKSPACE + if ($filePath.StartsWith($workspacePath)) { + $filePath = $filePath.Substring($workspacePath.Length).TrimStart('/', '\') + # Normalize to forward slashes for consistency + $filePath = $filePath -replace '\\', '/' + } + } + + # Create annotation title + $annotationTitle = "Test Failure: $description / $testName" + + # Build the annotation message + $annotationMessage = $message -replace "`n", "%0A" -replace "`r" + + # Build and output the workflow command + $workflowCommand = "::error file=$filePath" + if ($fileInfo.Line) { + $workflowCommand += ",line=$($fileInfo.Line)" + } + $workflowCommand += ",title=$annotationTitle::$annotationMessage" + + Write-Host $workflowCommand + + # Output a link to the test run + if ($env:GITHUB_SERVER_URL -and $env:GITHUB_REPOSITORY -and $env:GITHUB_RUN_ID) { + $logUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)" + Write-Host "Test logs: $logUrl" + } + } + } } @" diff --git a/build.psm1 b/build.psm1 index d09b7af925d..bf175c5f7c2 100644 --- a/build.psm1 +++ b/build.psm1 @@ -1864,6 +1864,69 @@ $stack_trace } +function Get-PesterFailureFileInfo +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$StackTraceString + ) + + # Parse stack trace to extract file path and line number + # Common patterns: + # "at line: 123 in C:\path\to\file.ps1" (Pester 4) + # "at C:\path\to\file.ps1:123" + # "at , C:\path\to\file.ps1: line 123" + # "at 1 | Should -Be 2, /path/to/file.ps1:123" (Pester 5) + # "at 1 | Should -Be 2, C:\path\to\file.ps1:123" (Pester 5 Windows) + + $result = @{ + File = $null + Line = $null + } + + if ([string]::IsNullOrWhiteSpace($StackTraceString)) { + return $result + } + + # Try pattern: "at line: 123 in " (Pester 4) + if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + $result.Line = $matches[1] + $result.File = $matches[2].Trim() + return $result + } + + # Try pattern: ", :123" (Pester 5 format) + # This handles both Unix paths (/path/file.ps1:123) and Windows paths (C:\path\file.ps1:123) + if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try pattern: "at :123" (without comma) + # Handle both absolute Unix and Windows paths + if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try pattern: ": line 123" + if ($StackTraceString -match '((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):\s*line\s+(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try to extract just the file path if no line number found + if ($StackTraceString -match '(?:at\s+|in\s+)?((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1)') { + $result.File = $matches[1].Trim() + } + + return $result +} + function Test-XUnitTestResults { param( From 6b8d724b030d1ced7bf772c502279d694c72bf47 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 03:57:33 +0000 Subject: [PATCH 186/255] Mark flaky Update-Help web tests as pending to unblock CI (#26796) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- ...ester-set-itresult-pattern.instructions.md | 198 ++++++++++++++++++ .../engine/Help/UpdatableHelpSystem.Tests.ps1 | 35 +++- 2 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 .github/instructions/pester-set-itresult-pattern.instructions.md diff --git a/.github/instructions/pester-set-itresult-pattern.instructions.md b/.github/instructions/pester-set-itresult-pattern.instructions.md new file mode 100644 index 00000000000..33a73ca081d --- /dev/null +++ b/.github/instructions/pester-set-itresult-pattern.instructions.md @@ -0,0 +1,198 @@ +--- +applyTo: + - "**/*.Tests.ps1" +--- + +# Pester Set-ItResult Pattern for Pending and Skipped Tests + +## Purpose + +This instruction explains when and how to use `Set-ItResult` in Pester tests to mark tests as Pending or Skipped dynamically within test execution. + +## When to Use Set-ItResult + +Use `Set-ItResult` when you need to conditionally mark a test as Pending or Skipped based on runtime conditions that can't be determined at test definition time. + +### Pending vs Skipped + +**Pending**: Use for tests that should be enabled but temporarily can't run due to: +- Intermittent external service failures (network, APIs) +- Known bugs being fixed +- Missing features being implemented +- Environmental issues that are being resolved + +**Skipped**: Use for tests that aren't applicable to the current environment: +- Platform-specific tests running on wrong platform +- Tests requiring specific hardware/configuration not present +- Tests requiring elevated permissions when not available +- Feature-specific tests when feature is disabled + +## Pattern + +### Basic Usage + +```powershell +It "Test description" { + if ($shouldBePending) { + Set-ItResult -Pending -Because "Explanation of why test is pending" + return + } + + if ($shouldBeSkipped) { + Set-ItResult -Skipped -Because "Explanation of why test is skipped" + return + } + + # Test code here +} +``` + +### Important: Always Return After Set-ItResult + +After calling `Set-ItResult`, you **must** return from the test to prevent further execution: + +```powershell +It "Test that checks environment" { + if ($env:SKIP_TESTS -eq 'true') { + Set-ItResult -Skipped -Because "SKIP_TESTS environment variable is set" + return # This is required! + } + + # Test assertions + $result | Should -Be $expected +} +``` + +**Why?** Without `return`, the test continues executing and may fail with errors unrelated to the pending/skipped condition. + +## Examples from the Codebase + +### Example 1: Pending for Intermittent Network Issues + +```powershell +It "Validate Update-Help for module" { + if ($markAsPending) { + Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541." + return + } + + Update-Help -Module $moduleName -Force + # validation code... +} +``` + +### Example 2: Skipped for Missing Environment + +```powershell +It "Test requires CI environment" { + if (-not $env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + return + } + + Install-CIPester -ErrorAction Stop +} +``` + +### Example 3: Pending for Platform-Specific Issue + +```powershell +It "Clear-Host works correctly" { + if ($IsARM64) { + Set-ItResult -Pending -Because "ARM64 runs in non-interactively mode and Clear-Host does not work." + return + } + + & { Clear-Host; 'hi' } | Should -BeExactly 'hi' +} +``` + +### Example 4: Skipped for Missing Feature + +```powershell +It "Test ACR authentication" { + if ($env:ACRTESTS -ne 'true') { + Set-ItResult -Skipped -Because "The tests require the ACRTESTS environment variable to be set to 'true' for ACR authentication." + return + } + + $psgetModuleInfo = Find-PSResource -Name $ACRTestModule -Repository $ACRRepositoryName + # test assertions... +} +``` + +## Alternative: Static -Skip and -Pending Parameters + +For conditions that can be determined at test definition time, use the static parameters instead: + +```powershell +# Static skip - condition known at definition time +It "Windows-only test" -Skip:(-not $IsWindows) { + # test code +} + +# Static pending - always pending +It "Test for feature being implemented" -Pending { + # test code that will fail until feature is done +} +``` + +**Use Set-ItResult when**: +- Condition depends on runtime state +- Condition is determined inside a helper function +- Need to check multiple conditions sequentially + +**Use static parameters when**: +- Condition is known at test definition +- Condition doesn't change during test run +- Want Pester to show the condition in test discovery + +## Best Practices + +1. **Always include -Because parameter** with a clear explanation +2. **Always return after Set-ItResult** to prevent further execution +3. **Reference issues or documentation** when relevant (e.g., "See issue #1234") +4. **Be specific in the reason** - explain what's wrong and what's needed +5. **Use Pending sparingly** - it indicates a problem that should be fixed +6. **Prefer Skipped over Pending** when test truly isn't applicable + +## Common Mistakes + +### ❌ Mistake 1: Forgetting to Return + +```powershell +It "Test" { + if ($condition) { + Set-ItResult -Pending -Because "Reason" + # Missing return - test code will still execute! + } + $value | Should -Be $expected # This runs and fails +} +``` + +### ❌ Mistake 2: Vague Reason + +```powershell +Set-ItResult -Pending -Because "Doesn't work" # Too vague +``` + +### ✅ Correct: + +```powershell +It "Test" { + if ($condition) { + Set-ItResult -Pending -Because "Update-Help has intermittent network timeouts. See issue #2807." + return + } + $value | Should -Be $expected +} +``` + +## See Also + +- [Pester Documentation: Set-ItResult](https://pester.dev/docs/commands/Set-ItResult) +- [Pester Documentation: It](https://pester.dev/docs/commands/It) +- Examples in the codebase: + - `test/powershell/Host/ConsoleHost.Tests.ps1` + - `test/infrastructure/ciModule.Tests.ps1` + - `tools/packaging/releaseTests/sbom.tests.ps1` diff --git a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 index e871a8d1603..6bf82d21af1 100644 --- a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 @@ -191,7 +191,8 @@ function RunUpdateHelpTests param ( [string]$tag = "CI", [switch]$useSourcePath, - [switch]$userscope + [switch]$userscope, + [switch]$markAsPending ) foreach ($moduleName in $modulesInBox) @@ -214,8 +215,15 @@ function RunUpdateHelpTests It ('Validate Update-Help for module ''{0}'' in {1}' -F $moduleName, [PSCustomObject] $updateScope) -Skip:(!(Test-CanWriteToPsHome) -and $userscope -eq $false) { + if ($markAsPending) { + Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541." + return + } + # Delete the whole help directory - Remove-Item ($moduleHelpPath) -Recurse -Force -ErrorAction SilentlyContinue + if ($moduleHelpPath) { + Remove-Item ($moduleHelpPath) -Recurse -Force -ErrorAction SilentlyContinue + } [hashtable] $UICultureParam = $(if ((Get-UICulture).Name -ne $myUICulture) { @{ UICulture = $myUICulture } } else { @{} }) [hashtable] $sourcePathParam = $(if ($useSourcePath) { @{ SourcePath = Join-Path $PSScriptRoot assets } } else { @{} }) @@ -246,8 +254,15 @@ function RunSaveHelpTests { try { - $saveHelpFolder = Join-Path $TestDrive (Get-Random).ToString() - New-Item $saveHelpFolder -Force -ItemType Directory > $null + $saveHelpFolder = if ($TestDrive) { + Join-Path $TestDrive (Get-Random).ToString() + } else { + $null + } + + if ($saveHelpFolder) { + New-Item $saveHelpFolder -Force -ItemType Directory > $null + } ## Save help has intermittent connectivity issues for downloading PackageManagement help content. ## Hence the test has been marked as Pending. @@ -283,7 +298,9 @@ function RunSaveHelpTests } finally { - Remove-Item $saveHelpFolder -Force -ErrorAction SilentlyContinue -Recurse + if ($saveHelpFolder) { + Remove-Item $saveHelpFolder -Force -ErrorAction SilentlyContinue -Recurse + } } } } @@ -316,7 +333,9 @@ Describe "Validate Update-Help from the Web for one PowerShell module." -Tags @( $ProgressPreference = $SavedProgressPreference } - RunUpdateHelpTests -Tag "CI" + ## Update-Help from the web has intermittent connectivity issues that cause CI failures. + ## Tests are marked as Pending to unblock work. See issues #2807 and #6541. + RunUpdateHelpTests -Tag "CI" -MarkAsPending } Describe "Validate Update-Help from the Web for one PowerShell module for user scope." -Tags @('CI', 'RequireAdminOnWindows', 'RequireSudoOnUnix') { @@ -328,7 +347,9 @@ Describe "Validate Update-Help from the Web for one PowerShell module for user s $ProgressPreference = $SavedProgressPreference } - RunUpdateHelpTests -Tag "CI" -UserScope + ## Update-Help from the web has intermittent connectivity issues that cause CI failures. + ## Tests are marked as Pending to unblock work. See issues #2807 and #6541. + RunUpdateHelpTests -Tag "CI" -UserScope -MarkAsPending } Describe "Validate Update-Help from the Web for all PowerShell modules." -Tags @('Feature', 'RequireAdminOnWindows', 'RequireSudoOnUnix') { From 08b13f24e6d359877b2cdbe650f7c8fc5ca20135 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 13 Feb 2026 14:43:16 -0500 Subject: [PATCH 187/255] Add Pester CI Analysis Skill (#26806) --- ...status-and-working-meaning.instructions.md | 299 ++++++++++ .../skills/analyze-pester-failures/SKILL.md | 524 ++++++++++++++++++ .../references/stack-trace-parsing.md | 163 ++++++ .../scripts/analyze-pr-test-failures.ps1 | 456 +++++++++++++++ .gitignore | 3 + 5 files changed, 1445 insertions(+) create mode 100644 .github/instructions/pester-test-status-and-working-meaning.instructions.md create mode 100644 .github/skills/analyze-pester-failures/SKILL.md create mode 100644 .github/skills/analyze-pester-failures/references/stack-trace-parsing.md create mode 100644 .github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 diff --git a/.github/instructions/pester-test-status-and-working-meaning.instructions.md b/.github/instructions/pester-test-status-and-working-meaning.instructions.md new file mode 100644 index 00000000000..d2b28a05f18 --- /dev/null +++ b/.github/instructions/pester-test-status-and-working-meaning.instructions.md @@ -0,0 +1,299 @@ +--- +applyTo: "**/*.Tests.ps1" +--- + +# Pester Test Status Meanings and Working Tests + +## Purpose + +This guide clarifies Pester test outcomes and what it means for a test to be "working" - which requires both **passing** AND **actually validating functionality**. + +## Test Statuses in Pester + +### Passed ✓ +**Status Code**: `Passed` +**Exit Result**: Test ran successfully, all assertions passed + +**What it means**: +- Test executed without errors +- All `Should` statements evaluated to true +- Test setup and teardown completed without issues +- Test is **validating** the intended functionality + +**What it does NOT mean**: +- The feature is working (assertions could be wrong) +- The test is meaningful (could be testing wrong thing) +- The test exercises all code paths + +### Failed ✗ +**Status Code**: `Failed` +**Exit Result**: Test ran but assertions failed + +**What it means**: +- Test executed but an assertion returned false +- Expected value did not match actual value +- Test detected a problem with the functionality + +**Examples**: +``` +Expected $true but got $false +Expected 5 items but got 3 +Expected no error but got: Cannot find parameter +``` + +### Error ⚠ +**Status Code**: `Error` +**Exit Result**: Test crashed with an exception + +**What it means**: +- Test failed to complete +- An exception was thrown during test execution +- Could be in test setup, test body, or test cleanup +- Often indicates environmental issue, not code functional issue + +**Examples**: +``` +Cannot bind argument to parameter 'Path' because it is null +File not found: C:\expected\config.json +Access denied writing to registry +``` + +### Pending ⏳ +**Status Code**: `Pending` +**Exit Result**: Test ran but never completed assertions + +**What it means**: +- Test was explicitly marked as not ready to run +- `Set-ItResult -Pending` was called +- Used to indicate: known bugs, missing features, environmental issues + +**When to use Pending**: +- Test for feature in development +- Test disabled due to known bug (issue #1234) +- Test disabled due to intermittent failures being fixed +- Platform-specific issues being resolved + +**⚠️ WARNING**: Pending tests are NOT validating functionality. They hide problems. + +### Skipped ⊘ +**Status Code**: `Skipped` +**Exit Result**: Test did not run (detected at start) + +**What it means**: +- Test was intentionally not executed +- `-Skip` parameter or `It -Skip:$condition` was used +- Environment doesn't support this test + +**When to use Skip**: +- Test not applicable to current platform (Windows-only test on Linux) +- Test requires feature that's not available (admin privileges) +- Test requires specific configuration not present + +**Difference from Pending**: +- **Skip**: "This test shouldn't run here" (known upfront) +- **Pending**: "This test should eventually run but can't now" + +### Ignored ✛ +**Status Code**: `Ignored` +**Exit Result**: Test marked as not applicable + +**What it means**: +- Test has `[Ignore("reason")]` attribute +- Test is permanently disabled in this location +- Not the same as Skipped (which is conditional) + +**When to use Ignore**: +- Test for deprecated feature +- Test for bug that won't be fixed +- Test moved to different test file + +--- + +## What Does "Working" Actually Mean? + +A test is **working** when it meets BOTH criteria: + +### 1. **Test Status is PASSED** ✓ +```powershell +It "Test name" { + # Test executes + # All assertions pass + # Returns Passed status +} +``` + +### 2. **Test Actually Validates Functionality** +```powershell +# ✓ GOOD: Tests actual functionality +It "Get-Item returns files from directory" -Tags @('Unit') { + $testDir = New-Item -ItemType Directory -Force + New-Item -Path $testDir -Name "file.txt" -ItemType File | Out-Null + + $result = Get-Item -Path "$testDir\file.txt" + + $result.Name | Should -Be "file.txt" + $result | Should -Exist + + Remove-Item $testDir -Recurse -Force +} + +# ✗ BAD: Returns Passed but doesn't validate functionality +It "Get-Item returns files from directory" -Tags @('Unit') { + $result = Get-Item -Path somepath # May not exist, may not actually test + $result | Should -Not -BeNullOrEmpty # Too vague +} + +# ✗ BAD: Test marked Pending - validation is hidden +It "Get-Item returns files from directory" -Tags @('Unit') { + Set-ItResult -Pending -Because "File system not working" + return + # No validation happens at all +} +``` + +--- + +## The Problem with Pending Tests + +### Why Pending Tests Hide Problems + +```powershell +# BAD: Test marked Pending - looks like "working" status but validation is skipped +It "Download help from web" { + Set-ItResult -Pending -Because "Web connectivity issues" + return + + # This code never runs: + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Result**: +- ✗ Feature is broken (Update-Help fails) +- ✓ Test shows "Pending" (looks acceptable) +- ✗ Problem is hidden and never fixed + +### The Right Approach + +**Option A: Fix the root cause** +```powershell +It "Download help from web" { + # Use local assets that are guaranteed to work + Update-Help -Module PackageManagement -SourcePath ./assets -Force -ErrorAction Stop + + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Option B: Gracefully skip when unavailable** +```powershell +It "Download help from web" -Skip:$(-not $hasInternet) { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Option C: Add retry logic for intermittent issues** +```powershell +It "Download help from web" { + $maxRetries = 3 + $attempt = 0 + + while ($attempt -lt $maxRetries) { + try { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + break + } + catch { + $attempt++ + if ($attempt -ge $maxRetries) { throw } + Start-Sleep -Seconds 2 + } + } + + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +--- + +## Test Status Summary Table + +| Status | Passed? | Validates? | Counts as "Working"? | Use When | +|--------|---------|------------|----------------------|----------| +| **Passed** | ✓ | ✓ | **YES** | Feature is working and test proves it | +| **Failed** | ✗ | ✓ | NO | Feature is broken or test has wrong expectation | +| **Error** | ✗ | ✗ | NO | Test infrastructure broken, can't validate | +| **Pending** | - | ✗ | **NO** ⚠️ | Temporary - test should eventually pass | +| **Skipped** | - | ✗ | NO | Test not applicable to this environment | +| **Ignored** | - | ✗ | NO | Test permanently disabled | + +--- + +## Recommended Patterns + +### Pattern 1: Resilient Test with Fallback +```powershell +It "Feature works with web or local source" { + $useLocal = $false + + try { + Update-Help -Module Package -Force -ErrorAction Stop + } + catch { + $useLocal = $true + Update-Help -Module Package -SourcePath ./assets -Force -ErrorAction Stop + } + + # Validate functionality regardless of source + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +### Pattern 2: Conditional Skip with Clear Reason +```powershell +Describe "Update-Help from Web" -Skip $(-not (Test-InternetConnectivity)) { + It "Downloads help successfully" { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty + } +} +``` + +### Pattern 3: Separate Suites by Dependency +```powershell +Describe "Help Content Tests - Web" { + # Tests that require internet - can be skipped if unavailable + It "Downloads from web" { ... } +} + +Describe "Help Content Tests - Local" { + # Tests with local assets - should always pass + It "Loads from local assets" { + Update-Help -Module Package -SourcePath ./assets -Force + Get-Help Get-Package | Should -Not -BeNullOrEmpty + } +} +``` + +--- + +## Checklist: Is Your Test "Working"? + +- [ ] Test status is **Passed** (not Pending, not Skipped, not Failed) +- [ ] Test actually **executes** the feature being tested +- [ ] Test has **specific assertions** (not just `Should -Not -BeNullOrEmpty`) +- [ ] Test includes **cleanup** (removes temp files, restores state) +- [ ] Test can run **multiple times** without side effects +- [ ] Test failure **indicates a real problem** (not flaky assertions) +- [ ] Test success **proves the feature works** (not just "didn't crash") + +If any of these is false, your test may be passing but not "working" properly. + +--- + +## See Also + +- [Pester Documentation](https://pester.dev/) +- [Set-ItResult Documentation](https://pester.dev/docs/commands/Set-ItResult) diff --git a/.github/skills/analyze-pester-failures/SKILL.md b/.github/skills/analyze-pester-failures/SKILL.md new file mode 100644 index 00000000000..ec1b0fe82ec --- /dev/null +++ b/.github/skills/analyze-pester-failures/SKILL.md @@ -0,0 +1,524 @@ +--- +name: analyze-pester-failures +description: Troubleshooting guide for analyzing and investigating Pester test failures in PowerShell CI jobs. Help agents understand why tests are failing, interpret test output, navigate test result artifacts, and provide actionable recommendations for fixing test issues. +--- + +# Analyze Pester Test Failures + +Investigate and troubleshoot Pester test failures in GitHub Actions workflows. Understand what tests are failing, why they're failing, and provide recommendations for test fixes. + +| Skill | When to Use | +|-------|-----------| +| analyze-pester-failures | When investigating why Pester tests are failing in a CI job. Use when a test job shows failures and you need to understand what test failed, why it failed, what the error message means, and what might need to be fixed. Also use when asked: "why did this test fail?", "what's the test error?", "test is broken", "test failure analysis", "debug test failure", or given test failure logs and stack traces. | + +## When to Use This Skill + +Use this skill when you need to: + +- Understand why a specific Pester test is failing +- Interpret test failure messages and error output +- Analyze test result data from CI workflow runs (XML, logs, stack traces) +- Identify the root cause of test failures (test logic, assertion failure, exception, timeout, skip/ignore reason) +- Provide recommendations for fixing failing tests +- Compare expected vs. actual test behavior +- Debug test environment issues (missing dependencies, configuration problems) +- Understand test skip/ignored/inconclusive status reasons + +**Do not use this skill for:** +- General PowerShell debugging unrelated to tests +- Test infrastructure/CI setup issues (except as they affect test failure interpretation) +- Performance analysis or benchmarking (that's a different investigation) + +## Quick Start + +### ⚠️ CRITICAL: The Workflow Must Be Followed IN ORDER + +This skill describes a **sequential 6-step analysis workflow**. Skipping steps or jumping around leads to **incomplete analysis and incorrect conclusions**. + +**The Problem**: It's easy to skip to Step 4 or 5 without doing Steps 1-2, resulting in missing data and bad conclusions. + +**The Solution**: Use the automated analysis script to enforce the workflow: + +```powershell +# Automatically runs Steps 1-6 in order, preventing skipping +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR + +# Example: +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR 26800 +``` + +This script: +1. ✓ Fetches PR status automatically +2. ✓ Downloads artifacts (can't skip, depends on Step 1) +3. ✓ Extracts failures (can't skip, depends on Step 2) +4. ✓ Analyzes error messages +5. ✓ Documents context +6. ✓ Generates recommendations + +**Only use the manual commands below if you fully understand the workflow.** + +### Manual Workflow (for reference) + +```powershell +# Step 1: Identify the failing job +gh pr view --json 'statusCheckRollup' | ConvertFrom-Json | Where-Object { $_.conclusion -eq 'FAILURE' } + +# Step 2: Download artifacts (extract RUN_ID from Step 1) +gh run download --dir ./artifacts +gh run view --log > test-logs.txt + +# Step 3-6: Extract, analyze, and interpret +# (See Analysis Workflow section below) +``` + +## Common Test Failure Analysis Approaches + +### 1. **Interpreting Assertion Failures** +The most common test failure is when an assertion doesn't match expectations. + +**Example:** +``` +Expected $true but got $false at /path/to/test.ps1:42 +Assertion failed: Should -Be "expected" but was "actual" +``` + +**How to analyze:** +- Read the assertion message: what was expected vs. what was actual? +- Check the test logic: is the expectation correct? +- Look for mock/stub issues: are dependencies configured correctly? +- Check parameter values: what inputs were passed to the function under test? + +### 2. **Exception Failures** +Tests fail when PowerShell throws an exception instead of successful completion. + +**Example:** +``` +Command: Write-Host $null +Error: Cannot bind argument to parameter 'Object' because it is null. +``` + +**How to analyze:** +- Read the exception message: what operation failed? +- Check the stack trace: where in the test or tested code did it throw? +- Verify preconditions: does the test setup provide required values/mocks? +- Look for environmental issues: missing modules, permissions, file system state? + +### 3. **Timeout Failures** +A test takes longer than the allowed timeout to complete. + +**Example:** +``` +Test 'Should complete in reasonable time' timed out after 30 seconds +``` + +**How to analyze:** +- Is the timeout appropriate for this test type? (network tests need more time) +- Is there an infinite loop in the test or tested code? +- Are there resource contention issues on the CI runner? +- Does the test hang waiting for something (file lock, network, process)? + +### 4. **Skip/Ignored Reason Analysis** +Tests marked as skipped or ignored provide clues about test environment. + +**Example:** +``` +Test marked [Skip("Only runs on Windows")] - running on Linux +Test marked [Ignore("Known issue #12345")] +``` + +**How to analyze:** +- Read the skip/ignore reason: is it still valid? +- Check if environment has changed: platform, module versions, etc. +- Verify issue status: is the known issue still open? Has it been fixed? +- Determine if skip should be removed or if test needs environment changes + +### 5. **Flaky/Intermittent Failures** +Tests that sometimes pass, sometimes fail indicate race conditions or environment sensitivity. + +**Example:** +- Test passes locally but fails on CI +- Test passes first run of suite, fails on second run +- Test passes on Windows but fails on Linux + +**How to analyze:** +- Look for timeout races: is timing involved in the test? +- Check for test isolation issues: does one test affect another? +- Verify environment differences: CI vs. local paths, permissions, versions +- Look for external dependencies: network calls, file I/O, process interactions + +## Key Artifacts and Locations + +| Item | Purpose | Location | +|------|---------|----------| +| Test result XML | Pester output with test cases, failures, errors | Workflow artifacts: `junit-pester-*.xml` | +| Job logs | Full job output including test execution and errors | GitHub Actions run logs or `gh run download` | +| Stack traces | Error location information from failed assertions | Within job logs and XML failure messages | +| Test files | The actual Pester test code (`.ps1` files) | `test/` directory in repository | + +## Analysis Workflow + +### ⚠️ Important: These Steps MUST Be Followed In Order + +Each step depends on the previous one. Skipping or re-ordering steps causes incomplete analysis: + +- **Step 1** (identify jobs) → You get the RUN_ID needed for Step 2 +- **Step 2** (download) → You get the artifacts needed for Step 3 +- **Step 3** (extract) → You discover what failures exist for Step 4 +- **Step 4** (read messages) → You understand the errors to analyze in Step 5 +- **Step 5** (context) → You gather information to make recommendations in Step 6 +- **Step 6** (interpret) → You use all above to recommend fixes + +**Real Problem We Had**: +- ❌ Jumped to Step 3 without Step 1-2 +- ❌ Used random test data from context instead of downloading PR artifacts +- ❌ Skipped Steps 5-6 entirely +- ❌ Made recommendations without full context + +**Result**: Wrong analysis and recommendations that didn't actually fix the problem. + +### Recommended: Use the Automated Script + +```powershell +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR +``` + +This enforces the workflow and prevents skipping. + +### Step 2: Get Test Results + +Fetch the test result artifacts and job logs: + +```powershell +# Download artifacts including test XML results +gh run download --dir ./artifacts + +# Get job logs +gh run view --log > test-logs.txt + +# Inspect test XML +$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml) +$xml.'test-results' | Select-Object total, failures, errors, ignored, inconclusive +``` + +### Step 3: Extract Specific Failures + +Find the failing test cases in the XML: + +```powershell +# Get all failed test cases +$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml) +$failures = $xml.SelectNodes('.//test-case[@result = "Failure"]') + +# For each failure, display key info +$failures | ForEach-Object { + [PSCustomObject]@{ + Name = $_.name + Description = $_.description + Message = $_.failure.message + StackTrace = $_.failure.'stack-trace' + } +} +``` + +### Step 4: Read the Error Message + +The error message tells you what went wrong: + +**Assertion failures:** +``` +Expected $true but got $false +Expected "value1" but got "value2" +Expression should have failed with exception, but didn't +``` + +**Exceptions:** +``` +Cannot find a parameter with name 'Name' +Property 'Property' does not exist on 'Object' +Cannot bind argument to parameter because it is null +``` + +**Timeouts:** +``` +Test timed out after 30 seconds +Test is taking too long to complete +``` + +### Step 5: Understand the Context + +Look at the test file to understand what was being tested: + +```powershell +# Find the test file mentioned in the stack trace +# Example: /path/to/test/Feature.Tests.ps1:42 + +# Read the test code around that line +code : + +# Understand: +# - What assertion is on that line? +# - What is the test trying to verify? +# - What are the setup/mock/before conditions? +# - Are there recent changes to the function being tested? +``` + +### Step 6: Interpret the Failure + +Determine the root cause category: + +**Test issue (needs code fix):** +- Assertion logic is wrong +- Test expectations don't match actual behavior +- Test setup is incomplete +- Mock/stub configuration missing + +**Environmental issue (needs environment change):** +- Test assumes a specific file or registry entry exists +- Test requires Windows/Linux specifically +- Test requires specific PowerShell version +- Test requires specific module version +- Timing-sensitive test affected by CI load + +**Data issue (needs input data change):** +- Test data no longer valid +- External API changed format +- Configuration file has changed structure + +**Flakiness (needs test hardening):** +- Race condition in test +- Timing assumptions too tight +- Resource contention on CI runner +- Non-deterministic behavior in tested code + +## Common Test Failure Patterns + +| Pattern | What It Means | Example | Next Step | +|---------|---------------|---------|-----------| +| `Expected $true but got $false` | Assertion on boolean result failed | Test expects function returns true, but it returns false | Check function logic for bug or test logic for wrong expectation | +| `Cannot find path` | File or directory doesn't exist | Test tries to read config file that's not present | Verify file path, check test setup, ensure CI environment has file | +| `Cannot bind argument to parameter 'X'` | Required parameter value is null or wrong type | Function called with $null where object expected | Check test mock setup, verify parameter types | +| `Test timed out after X seconds` | Test exceeded time limit | Network call or loop takes too long | Increase timeout for slow test, find infinite loop, mock network calls | +| `Expression should have failed but didn't` | Exception wasn't thrown when expected | Test expects error but function succeeds | Check if function behavior changed, update test expectation | +| `Could not find parameter 'X'` | Function doesn't have parameter | Test calls function with parameter that doesn't exist | Check PowerShell version, verify function signature, update test | +| `This platform is not supported` | Test skipped on current OS | Windows-only test running on Linux | Add platform check, update test environment, or mark as platform-specific | +| `Test marked [Ignore]` | Test explicitly disabled | Test has `[Ignore("reason")]` attribute | Check if reason still valid, remove if issue fixed | + +## Interpreting Test Results + +### Test Result Counts + +Pester test outcomes are categorized as: + +| Count | Meaning | Notes | +|-------|---------|-------| +| `total` | Total number of test cases executed | Should match: passed + failed + errors + skipped + ignored | +| `failures` | Test assertions that failed | `Expected X but got Y` type failures | +| `errors` | Tests that threw exceptions | Unhandled PowerShell exceptions during test | +| `skipped` | Tests explicitly skipped (marked with `-Skip`) | Test code recognizes condition and skips | +| `ignored` | Tests marked as ignored (marked with `-Ignore`) | Test disabled intentionally, usually notes reason | +| `inconclusive` | Tests with unclear result | Rare; usually means test framework issue | +| `passed` | Tests with passing assertions | `total - failures - errors - skipped - ignored` | + +### Stack Trace Interpretation + +A stack trace shows where the failure occurred: + +``` +at /home/runner/work/PowerShell/test/Feature.Tests.ps1:42 + +Means: +- File: /home/runner/work/PowerShell/test/Feature.Tests.ps1 +- Line: 42 +- Look at that line to see which assertion failed +``` + +### Understanding Skipped Tests + +When XML shows `result="Ignored"` or `result="Skipped"`: + +```xml + + Only runs on Windows + +``` + +The reason explains why test didn't run. Not a failure, but important for understanding test coverage. + +## Providing Test Failure Analysis + +### Investigation Questions + +After gathering test output, ask yourself: + +1. **Is the test code correct?** + - Does the test assertion match the expected behavior? + - Are test expectations still valid? + - Has the function being tested changed? + +2. **Is the test setup correct?** + - Are mocks/stubs configured properly? + - Does the test environment have required files/configuration? + - Are preconditions (database, files, services) met? + +3. **Is this a code bug or test issue?** + - Does the tested function have a logic error? + - Or does the test have incorrect expectations? + +4. **Is this environment-specific?** + - Only fails on Windows/Linux? + - Only fails on CI but passes locally? + - Timing-dependent or resource-dependent? + +5. **Is this a known/expected failure?** + - Is there already an issue tracking this failure? + - Is the test marked as flaky or expected to fail? + - Does the skip/ignore reason still apply? + +### Recommendation Framework + +Based on your analysis: + +| Finding | Recommendation | +|---------|-----------------| +| Test logic is wrong | "Test assertion on line X is incorrect. Test expects Y but function correctly returns Z. Update test expectation." | +| Tested code has bug | "Function at file.ps1#L42 has logic error. When X happens, returns Y instead of Z. Fix the condition." | +| Missing test setup | "Test setup incomplete. Mock for dependency Y is not configured. Add `Mock Get-Y -MockWith { ... }`" | +| Environment issue | "Test is Windows-specific but running on Linux. Either add platform check or skip on non-Windows." | +| Flaky test | "Test is timing-sensitive (sleep 1 second). Increase timeout or use better synchronization." | +| Test should be skipped | "Test is marked Ignored for good reason. Keep it disabled until issue #12345 is fixed." | + +### Tone and Structure + +Provide analysis as: + +1. **Summary** (1 sentence): What test is failing and general category +2. **Failure Details** (2-3 sentences): What the test output says +3. **Root Cause** (1-2 sentences): Why it's failing (test bug vs. code bug vs. environment) +4. **Recommendation** (actionable): What should be done to fix it +5. **Context** (optional): Link to related code, issues, or recent changes + +## Examples + +### Example 1: Assertion Failure Due to Code Bug + +**Test Output:** +``` +Expected 5 but got 3 at /path/to/Test.ps1:42 +``` + +**Investigation:** +1. Look at line 42: `$result | Should -Be 5` +2. Check the test: It expects function to return 5 items +3. Check the function: It returns `$items | Where-Object {$_.Status -eq "Active"}` but the filter is wrong +4. Root cause: Function has logic error, not test error + +**Recommendation:** +``` +Test failure is due to a code bug: + +The test Set-Configuration should return 5 items but returns 3. + +Looking at the tested function at [module.ps1#L42](module.ps1#L42): + $activeItems = $items | Where-Object {$_.Status -eq "Active"} + +The issue is the filter condition. It's currently filtering by "Active" status, +but should include "Pending" status as well. + +Fix: Change line 42 to: + $activeItems = $items | Where-Object {$_.Status -ne "Disabled"} + +Then re-run the test to verify it now returns 5 items as expected. +``` + +### Example 2: Test Setup Issue + +**Test Output:** +``` +Cannot find path '/expected/config.json' because it does not exist at /path/to/Test.ps1:15 +``` + +**Investigation:** +1. Line 15 tries to read a config file +2. The test setup doesn't create this file +3. Works locally but fails on CI because CI doesn't have the same file + +**Recommendation:** +``` +Test setup is incomplete: + +The test Initialize-Config fails because it expects /expected/config.json but the test doesn't create this file. + +The test needs to ensure the config file exists. Currently line 12-14 doesn't set up the file: + + # Before: + # (no setup of config file) + + # After: + @{ setting1 = "value1"; setting2 = "value2" } | ConvertTo-Json | + Out-File $testConfigPath + +Alternatively, the test function should accept a parameter for the config path and use a temporary file: + param([string]$ConfigPath = (New-TemporaryFile)) + +Re-run the test to verify the config file is properly available. +``` + +### Example 3: Platform-Specific Test Failure + +**Test Output:** +``` +Test 'should read Windows Registry' failed on Linux runner +Cannot find path 'HKEY_LOCAL_MACHINE:\...' +``` + +**Investigation:** +1. Test assumes Windows Registry exists (Windows-only) +2. Running on Linux runner doesn't have Registry +3. Test should skip on non-Windows platforms + +**Recommendation:** +``` +Test is platform-specific but running on wrong platform: + +The test "should read Windows Registry" assumes Windows Registry exists but is running on Linux. + +Add a platform check to skip this test on non-Windows systems: + + It "should read Windows Registry" -Skip:$(-not $IsWindows) { + # test code here + } + +Or group Windows-only tests in a separate Describe block with platform check: + + Describe "Windows Registry Tests" -Skip:$(-not $IsWindows) { + # all Windows-specific tests here + } + +This allows the test to be skipped on Linux/Mac while still running on Windows CI. +``` + +## References + +- [Pester Testing Framework](https://pester.dev/) — Official documentation, best practices for test writing +- [Test Files](../../../test/) — PowerShell test suite in repository +- [GitHub Actions Documentation](https://docs.github.com/en/actions) — Understanding workflow runs and logs +- [PowerShell Documentation](https://learn.microsoft.com/en-us/powershell/) — Language reference for understanding test code + +## Tips + +1. **Read the error message first:** The error message is usually the most direct clue to the problem +2. **Check test vs. code blame:** Is the test wrong or is the code wrong? Look at both sides +3. **Verify test isolation:** Does one test failure affect others? Check for shared state or test ordering dependencies +4. **Test locally first:** Try running the failing test locally to reproduce and understand it better +5. **Check for environmental assumptions:** Windows-specific paths, module versions, file locations may differ on CI +6. **Look for skip/ignore patterns:** If a test is consistently ignored, check if the reason is still valid +7. **Compare passing vs. failing:** If test passes locally but fails on CI, the difference is usually environment-related +8. **Check recent changes:** Did a recent PR change the tested code or test itself? +9. **Understand Pester output format:** Different Pester versions, different `-ErrorAction`, `-WarningAction` produce different test results +10. **Don't assume CI is wrong:** Failures on CI often reveal real issues that local testing missed (network, file permissions, parallelization, etc.) + +## Additional Links + +- [PowerShell Repository](https://github.com/PowerShell/PowerShell) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Pester Testing Framework](https://github.com/Pester/Pester) diff --git a/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md b/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md new file mode 100644 index 00000000000..707f45560a9 --- /dev/null +++ b/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md @@ -0,0 +1,163 @@ +# Understanding Pester Test Failures + +This reference explains how to interpret Pester test output and understand failure messages. + +## Supported Formats + +### Pester 4 Format +``` +at line: 123 in C:\path\to\file.ps1 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + $result.Line = $matches[1] + $result.File = $matches[2].Trim() + return $result +} +``` + +### Pester 5 Format (Common) +``` +at 1 | Should -Be 2, C:\path\to\file.ps1:123 +at 1 | Should -Be 2, /home/runner/work/PowerShell/PowerShell/test/file.ps1:123 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} +``` + +### Alternative Format +``` +at C:\path\to\file.ps1:123 +at /path/to/file.ps1:123 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} +``` + +## Troubleshooting Parsing Failures + +### Issue: Line Number Extracted But File Path Is Null + +**Cause:** Stack trace matches line-with-path pattern but file extraction doesn't work + +**Solution:** +1. Check if file path exists as expected in filesystem +2. Verify regex doesn't have too-greedy bounds (check use of `.+?` vs `.+`) +3. Test regex against actual stack trace string: + ```powershell + $trace = "at line: 42 in C:\path\to\test.ps1" + if ($trace -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + Write-Host "File: $($matches[2])" # Should be "C:\path\to\test.ps1" + } + ``` + +### Issue: Special Characters in File Path Break Regex + +**Cause:** Characters like parens `()`, brackets `[]`, pipes `|` have special meaning in regex + +**Solution:** +1. Escape special chars in regex: `[Regex]::Escape($path)` +2. Use character class `[\/\\]` instead of alternation for path separators +3. Test with files containing problematic names: + ```powershell + $traces = @( + "at line: 1 in C:\path\(with)\parens\test.ps1", + "at /home/user/[brackets]/test.ps1:5", + "at C:\path\with spaces\test.ps1:10" + ) + # Test each against all patterns + ``` + +### Issue: Regex Matches But Extracts Wrong Values + +**Symptom:** $matches[1] is file instead of line, or vice versa + +**Debug Steps:** +1. Print all captured groups: `$matches.Values | Format-Table -AutoSize` +2. Verify group order in regex matches expectations +3. Test with sample Pester output: + ```powershell + $sampleTrace = @" + at 1 | Should -Be 2, /home/runner/work/PowerShell/test/file.ps1:42 + "@ + + if ($sampleTrace -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + Write-Host "Match 1: $($matches[1])" # Should be file path + Write-Host "Match 2: $($matches[2])" # Should be line number + } + ``` + +## Testing the Parser + +Use this PowerShell script to validate `Get-PesterFailureFileInfo`: + +```powershell +# Import the function +. ./build.psm1 + +$testCases = @( + @{ + Input = "at line: 42 in C:\path\to\test.ps1" + Expected = @{ File = "C:\path\to\test.ps1"; Line = "42" } + }, + @{ + Input = "at /home/runner/work/test.ps1:123" + Expected = @{ File = "/home/runner/work/test.ps1"; Line = "123" } + }, + @{ + Input = "at 1 | Should -Be 2, /path/to/file.ps1:99" + Expected = @{ File = "/path/to/file.ps1"; Line = "99" } + } +) + +foreach ($test in $testCases) { + $result = Get-PesterFailureFileInfo -StackTraceString $test.Input + + $fileMatch = $result.File -eq $test.Expected.File + $lineMatch = $result.Line -eq $test.Expected.Line + $status = if ($fileMatch -and $lineMatch) { "✓ PASS" } else { "✗ FAIL" } + + Write-Host "$status : $($test.Input)" + if (-not $fileMatch) { Write-Host " Expected file: $($test.Expected.File), got: $($result.File)" } + if (-not $lineMatch) { Write-Host " Expected line: $($test.Expected.Line), got: $($result.Line)" } +} +``` + +## Adding Support for New Formats + +When Pester changes its output format: + +1. **Capture sample output** from failing tests +2. **Identify the pattern** (e.g., "file path always after comma followed by colon") +3. **Write regex** to match pattern without over-matching +4. **Add to `Get-PesterFailureFileInfo`** before existing patterns (order matters for fallback) +5. **Test with samples** containing special characters, long paths, and edge cases + +Example: Adding a new format at the top of the function: + +```powershell +# Try pattern: "at , :" (Pester 5.1 hypothetical) +if ($StackTraceString -match 'at .+?, ((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} + +# Try existing patterns... +``` + +Place new patterns **first** so they take precedence over fallback patterns. diff --git a/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 b/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 new file mode 100644 index 00000000000..12486596071 --- /dev/null +++ b/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 @@ -0,0 +1,456 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Automated Pester test failure analysis workflow for GitHub PRs. + +.DESCRIPTION + This script automates the complete analysis workflow defined in the analyze-pester-failures + skill. It performs all steps in order: + 1. Identify failing test jobs in the PR + 2. Download test artifacts and logs + 3. Extract specific test failures + 4. Parse error messages + 5. Search logs for error markers and generate recommendations + + By automating the workflow, this ensures analysis steps are followed in order + and nothing is skipped. + +.PARAMETER PR + The GitHub PR number to analyze (e.g., 26800) + +.PARAMETER Owner + Repository owner (default: PowerShell) + +.PARAMETER Repo + Repository name (default: PowerShell) + +.PARAMETER OutputDir + Directory to store analysis results (default: ./pester-analysis-PR) + +.PARAMETER Interactive + Prompt for recommendations after analysis (default: non-interactive) + +.PARAMETER ForceDownload + Force re-download of artifacts and logs, even if they already exist + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 + Analyzes PR #26800 and saves results to ./pester-analysis-PR26800 + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -Interactive + Interactive mode: shows failures and prompts for next steps + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -ForceDownload + Re-download all logs and artifacts, skipping the cache + +.NOTES + Requires: GitHub CLI (gh) configured and authenticated + This script enforces the workflow defined in .github/skills/analyze-pester-failures/SKILL.md +#> + +param( + [Parameter(Mandatory)] + [int]$PR, + + [string]$Owner = 'PowerShell', + [string]$Repo = 'PowerShell', + [string]$OutputDir, + [switch]$Interactive, + [switch]$ForceDownload +) + +$ErrorActionPreference = 'Stop' + +if (-not $OutputDir) { + $OutputDir = "./pester-analysis-PR$PR" +} + +# Colors for output +$colors = @{ + Step = [ConsoleColor]::Cyan + Success = [ConsoleColor]::Green + Warning = [ConsoleColor]::Yellow + Error = [ConsoleColor]::Red + Info = [ConsoleColor]::Gray +} + +function Write-Step { + param([string]$text, [int]$number) + Write-Host "`n[$number/6] $text" -ForegroundColor $colors.Step -BackgroundColor Black +} + +function Write-Result { + param([string]$text, [ValidateSet('Success','Warning','Error','Info')]$type = 'Info') + Write-Host $text -ForegroundColor $colors[$type] +} + +Write-Host "`n=== Pester Test Failure Analysis ===" -ForegroundColor $colors.Step +Write-Host "PR: $Owner/$Repo#$PR" -ForegroundColor $colors.Info +Write-Host "Output Directory: $OutputDir" -ForegroundColor $colors.Info + +# Ensure output directory exists +if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null +} + +# STEP 1: Identify the Failing Test Job +Write-Step "Identify failing test jobs" 1 + +Write-Result "Fetching PR status checks..." Info +$prResponse = gh pr view $PR --repo "$Owner/$Repo" --json 'statusCheckRollup' | ConvertFrom-Json +$allChecks = $prResponse.statusCheckRollup + +$failedJobs = $allChecks | Where-Object { $_.conclusion -eq 'FAILURE' } + +if (-not $failedJobs) { + Write-Result "✓ No failed jobs found" Success + Write-Host " Total checks: $($allChecks.Count)" + $allChecks | Where-Object { $_ } | ForEach-Object { + Write-Host " - $($_.name): $($_.conclusion)" -ForegroundColor $colors.Info + } + exit 0 +} + +Write-Result "✓ Found $($failedJobs.Count) failing job(s)" Warning + +$failedJobs | Where-Object { $_.conclusion -eq 'FAILURE' } | ForEach-Object { + Write-Host " ✗ $($_.name) - $($_.conclusion)" -ForegroundColor $colors.Error + if ($_.detailsUrl) { + Write-Host " URL: $($_.detailsUrl)" -ForegroundColor $colors.Info + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 2..." + Read-Host | Out-Null +} + +# STEP 2: Get Test Results +Write-Step "Download test artifacts and logs" 2 + +# Extract unique run IDs from failing jobs +$uniqueRuns = @() +foreach ($failedJob in $failedJobs) { + if ($failedJob.detailsUrl -match 'runs/(\d+)') { + $runId = $matches[1] + if ($runId -notin $uniqueRuns) { + $uniqueRuns += $runId + } + } +} + +if ($uniqueRuns.Count -eq 0) { + Write-Result "✗ Could not extract run IDs from failing jobs" Error + exit 1 +} + +Write-Result "Found $($uniqueRuns.Count) run(s): $($uniqueRuns -join ', ')" Info + +$artifactDir = Join-Path $OutputDir artifacts + +# Check if artifacts already exist +$existingArtifacts = Get-ChildItem $artifactDir -Recurse -File -ErrorAction SilentlyContinue + +if ($existingArtifacts -and -not $ForceDownload) { + Write-Result "✓ Artifacts already downloaded" Success + $existingArtifacts | ForEach-Object { + Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info + } +} else { + Write-Result "Downloading artifacts from run $($uniqueRuns[0])..." Info + gh run download $uniqueRuns[0] --dir $artifactDir --repo "$Owner/$Repo" 2>&1 | Out-Null + + if (Test-Path $artifactDir) { + Write-Result "✓ Artifacts downloaded" Success + Get-ChildItem $artifactDir -Recurse -File | ForEach-Object { + Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info + } + } else { + Write-Result "✗ Failed to download artifacts" Error + exit 1 + } +} + +# Download individual job logs for failing jobs +Write-Result "Downloading individual job logs..." Info + +$logsDir = Join-Path $OutputDir "logs" +if (-not (Test-Path $logsDir)) { + New-Item -ItemType Directory -Path $logsDir -Force | Out-Null +} + +# Check if logs already exist +$existingLogs = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue + +if ($existingLogs -and -not $ForceDownload) { + Write-Result "✓ Job logs already downloaded" Success + $existingLogs | ForEach-Object { + Write-Host " - $($_.Name)" -ForegroundColor $colors.Info + } +} else { + # Process each run and get its jobs + $failedJobIds = @() + foreach ($runId in $uniqueRuns) { + $runJobs = gh run view $runId --repo "$Owner/$Repo" --json jobs | ConvertFrom-Json + + foreach ($failedJob in $failedJobs) { + # Check if this failed job belongs to this run + if ($failedJob.detailsUrl -match "runs/$runId/") { + $jobMatch = $runJobs.jobs | Where-Object { $_.name -eq $failedJob.name } | Select-Object -First 1 + if ($jobMatch) { + $failedJobIds += @{ + name = $failedJob.name + id = $jobMatch.databaseId + runId = $runId + } + } + } + } + } + + # Download logs for all failed jobs + foreach ($jobInfo in $failedJobIds) { + $logFile = Join-Path $logsDir ("log-{0}.txt" -f ($jobInfo.name -replace '[^a-zA-Z0-9-]', '_')) + Write-Result " Downloading: $($jobInfo.name) (Run $($jobInfo.runId))" Info + gh run view $jobInfo.runId --log --job $jobInfo.id --repo "$Owner/$Repo" > $logFile 2>&1 + } + + Write-Result "✓ Job logs downloaded" Success + Get-ChildItem $logsDir -Filter "*.txt" | ForEach-Object { + Write-Host " - $($_.Name)" -ForegroundColor $colors.Info + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 3..." + Read-Host | Out-Null +} + +# STEP 3: Extract Specific Failures +Write-Step "Extract test failures from XML" 3 + +$xmlFiles = Get-ChildItem $artifactDir -Filter "*.xml" -Recurse +if (-not $xmlFiles) { + Write-Result "✗ No test result XML files found" Error + exit 1 +} + +Write-Result "✓ Found $($xmlFiles.Count) test result file(s)" Success + +$allFailures = @() + +foreach ($xmlFile in $xmlFiles) { + Write-Result "`nParsing: $($xmlFile.Name)" Info + + try { + [xml]$xml = Get-Content $xmlFile + $testResults = $xml.'test-results' + + Write-Host " Total: $($testResults.total)" -ForegroundColor $colors.Info + Write-Host " Passed: $($testResults.passed)" -ForegroundColor $colors.Success + if ($testResults.failures -gt 0) { + Write-Host " Failed: $($testResults.failures)" -ForegroundColor $colors.Error + } + if ($testResults.errors -gt 0) { + Write-Host " Errors: $($testResults.errors)" -ForegroundColor $colors.Error + } + if ($testResults.skipped -gt 0) { + Write-Host " Skipped: $($testResults.skipped)" -ForegroundColor $colors.Warning + } + if ($testResults.ignored -gt 0) { + Write-Host " Ignored: $($testResults.ignored)" -ForegroundColor $colors.Warning + } + + # Extract failures + $failures = $xml.SelectNodes('.//test-case[@result = "Failure"]') + + foreach ($failure in $failures) { + $allFailures += @{ + Name = $failure.name + File = $xmlFile.Name + Message = $failure.failure.message + StackTrace = $failure.failure.'stack-trace' + } + } + } catch { + Write-Result "✗ Error parsing XML: $_" Error + } +} + +Write-Result "`n✓ Extracted $($allFailures.Count) failures total" Success + +# Save failures to JSON for later analysis +$allFailures | ConvertTo-Json -Depth 10 | Out-File (Join-Path $OutputDir "failures.json") + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 4..." + Read-Host | Out-Null +} + +# STEP 4: Read Error Messages +Write-Step "Analyze error messages" 4 + +$failuresByType = @{} + +foreach ($failure in $allFailures) { + $message = $failure.Message -split "`n" | Select-Object -First 1 + + # Categorize failure + $type = 'Other' + if ($message -match 'Expected .* but got') { $type = 'Assertion' } + elseif ($message -match 'Cannot (find|bind)') { $type = 'Exception' } + elseif ($message -match 'timed out') { $type = 'Timeout' } + + if (-not $failuresByType[$type]) { + $failuresByType[$type] = @() + } + $failuresByType[$type] += $failure +} + +Write-Result "Failure breakdown:" Info +$failuresByType.GetEnumerator() | ForEach-Object { + Write-Host " $($_.Key): $($_.Value.Count)" -ForegroundColor $colors.Warning +} + +Write-Result "`nTop failure messages:" Info +$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 3 | ForEach-Object { + Write-Host " [$($_.Count)x] $($_.Name -split "`n" | Select-Object -First 1)" -ForegroundColor $colors.Info +} + +# Save analysis +$analysis = @{ + FailuresByType = @{} + TopMessages = @() +} + +$failuresByType.GetEnumerator() | ForEach-Object { + $analysis.FailuresByType[$_.Key] = $_.Value.Count +} + +$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object { + $analysis.TopMessages += @{ + Count = $_.Count + Message = ($_.Name -split "`n" | Select-Object -First 1) + } +} + +$analysis | ConvertTo-Json | Out-File (Join-Path $OutputDir "analysis.json") + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 5..." + Read-Host | Out-Null +} + +# STEP 5: Search Logs for Error Markers +Write-Step "Search logs for error markers" 5 + +$logsDir = Join-Path $OutputDir "logs" +if (-not (Test-Path $logsDir)) { + Write-Result "⚠ Logs directory not found" Warning +} else { + $logFiles = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue + if (-not $logFiles) { + Write-Result "⚠ No log files found in logs directory" Warning + } else { + Write-Result "Searching $($logFiles.Count) job log(s) for error markers ([-])" Info + Write-Result "Format: [JobName] [LineNumber] Content" Info + Write-Host "" + + $allErrorLines = @() + + foreach ($logFile in $logFiles) { + $jobName = $logFile.BaseName -replace '^log-', '' + $logLines = @(Get-Content $logFile) + + for ($i = 0; $i -lt $logLines.Count; $i++) { + $line = $logLines[$i] + if ($line -match '\s\[-\]\s') { + $allErrorLines += @{ + JobName = $jobName + LineNumber = $i + 1 + Content = $line + } + } + } + } + + if ($allErrorLines.Count -gt 0) { + Write-Result "✓ Found $($allErrorLines.Count) error marker line(s)" Warning + + $allErrorLines | ForEach-Object { + Write-Host " [$($_.JobName)] [$($_.LineNumber)] $($_.Content)" -ForegroundColor $colors.Error + } + + # Save to file + $allErrorLines | ConvertTo-Json | Out-File (Join-Path $OutputDir "error-markers.json") + Write-Result "✓ Error markers saved to error-markers.json" Success + } else { + Write-Result "✓ No error markers found in logs" Success + } + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 6..." + Read-Host | Out-Null +} + +# STEP 6: Generate Recommendations +Write-Step "Generate recommendations" 6 + +$recommendations = @() + +# Analyze patterns +if ($failuresByType['Assertion']) { + $recommendations += "Multiple assertion failures detected. These indicate test expectations don't match actual behavior." +} + +if ($failuresByType['Exception']) { + $recommendations += "Exception errors found. Check test setup and prerequisites - may indicate missing files, modules, or permissions." +} + +if ($failuresByType['Timeout']) { + $recommendations += "Timeout failures suggest slow or hanging operations. Consider network issues or resource constraints on CI." +} + +# Check for patterns in failure messages +$failureMessages = $allFailures.Message -join "`n" +if ($failureMessages -match 'PackageManagement') { + $recommendations += "PackageManagement module issues detected. Verify module availability and help repository access." +} + +if ($failureMessages -match 'Update-Help') { + $recommendations += "Update-Help failures detected. Check network connectivity to help repository and help installation paths." +} + +Write-Result "`n📋 Recommendations:" Info +if ($recommendations) { + $recommendations | ForEach-Object { Write-Host " • $_" -ForegroundColor $colors.Info } +} else { + Write-Host " • Review failures in detail" -ForegroundColor $colors.Info + Write-Host " • Check if test changes are needed" -ForegroundColor $colors.Info + Write-Host " • Consider environment-specific issues" -ForegroundColor $colors.Info +} + +$recommendations | Out-File (Join-Path $OutputDir "recommendations.txt") + +# Summary +Write-Host "`n=== Analysis Complete ===" -ForegroundColor $colors.Step +Write-Host "Results saved to: $OutputDir" -ForegroundColor $colors.Info +Write-Host " - failures.json (detailed failure data)" -ForegroundColor $colors.Info +Write-Host " - analysis.json (summary analysis)" -ForegroundColor $colors.Info +Write-Host " - recommendations.txt (suggested fixes)" -ForegroundColor $colors.Info +Write-Host " - error-markers.json (error markers from logs)" -ForegroundColor $colors.Info +Write-Host " - logs/ (individual job log files)" -ForegroundColor $colors.Info +Write-Host " - artifacts/ (downloaded test artifacts)" -ForegroundColor $colors.Info + +Write-Host "`nNext steps:" -ForegroundColor $colors.Step +Write-Host "1. Review recommendations.txt for analysis" -ForegroundColor $colors.Info +Write-Host "2. Examine failures.json for detailed error messages" -ForegroundColor $colors.Info +Write-Host "3. Check error-markers.json for specific test failures in logs" -ForegroundColor $colors.Info +Write-Host "4. Review individual job logs in logs/ directory for contextual details" -ForegroundColor $colors.Info +Write-Host "`n" diff --git a/.gitignore b/.gitignore index f115e61e22d..48556cf1b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -119,5 +119,8 @@ assets/manpage/*.gz tmp/* .env.local +# Pester test failure analysis results (generated by analyze-pr-test-failures.ps1) +**/pester-analysis-*/ + # Ignore CTRF report files crtf/* From 8f457fc7140a758b1434940fc71320eef54e7874 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 17 Feb 2026 13:40:04 -0800 Subject: [PATCH 188/255] Skip the flaky `Update-Help` test for the `PackageManagement` module (#26845) --- .../engine/Help/UpdatableHelpSystem.Tests.ps1 | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 index 6bf82d21af1..155654b3b80 100644 --- a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 @@ -215,7 +215,7 @@ function RunUpdateHelpTests It ('Validate Update-Help for module ''{0}'' in {1}' -F $moduleName, [PSCustomObject] $updateScope) -Skip:(!(Test-CanWriteToPsHome) -and $userscope -eq $false) { - if ($markAsPending) { + if ($markAsPending -or ($IsLinux -and $moduleName -eq "PackageManagement")) { Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541." return } @@ -333,9 +333,7 @@ Describe "Validate Update-Help from the Web for one PowerShell module." -Tags @( $ProgressPreference = $SavedProgressPreference } - ## Update-Help from the web has intermittent connectivity issues that cause CI failures. - ## Tests are marked as Pending to unblock work. See issues #2807 and #6541. - RunUpdateHelpTests -Tag "CI" -MarkAsPending + RunUpdateHelpTests -Tag "CI" } Describe "Validate Update-Help from the Web for one PowerShell module for user scope." -Tags @('CI', 'RequireAdminOnWindows', 'RequireSudoOnUnix') { @@ -347,9 +345,7 @@ Describe "Validate Update-Help from the Web for one PowerShell module for user s $ProgressPreference = $SavedProgressPreference } - ## Update-Help from the web has intermittent connectivity issues that cause CI failures. - ## Tests are marked as Pending to unblock work. See issues #2807 and #6541. - RunUpdateHelpTests -Tag "CI" -UserScope -MarkAsPending + RunUpdateHelpTests -Tag "CI" -UserScope } Describe "Validate Update-Help from the Web for all PowerShell modules." -Tags @('Feature', 'RequireAdminOnWindows', 'RequireSudoOnUnix') { From f3b5603b5dfcb0cc209e9e6c2a8e9a0fdaf16236 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:16:54 -0800 Subject: [PATCH 189/255] Bump github/codeql-action from 4.32.2 to 4.32.3 (#26839) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index c5a0b9ff985..96fb89287d6 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3.29.5 + uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3.29.5 + uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a5d05091555..422381cf50c 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3.29.5 + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5 with: sarif_file: results.sarif From 9d68a05ac3f68f07ea6daed31cb95bf3039a4fb8 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Thu, 19 Feb 2026 04:04:45 +0900 Subject: [PATCH 190/255] Add comprehensive `PSCustomObject` tests for `ConvertTo-Json` (#26743) --- .../ConvertTo-Json.Tests.ps1 | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) 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 4fe4b0d59b6..8be8631ee6b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 @@ -1043,6 +1043,250 @@ Describe 'ConvertTo-Json' -tags "CI" { #endregion Comprehensive Array and Dictionary Tests (Phase 2) + + #region Comprehensive PSCustomObject Tests (Phase 3) + # Test coverage for ConvertTo-Json PSCustomObject serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, nested structures + + Context 'PSCustomObject basic serialization' { + It 'Should serialize PSCustomObject with single property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Name = 'Test' } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"Name":"Test"}' + $jsonInputObject | Should -BeExactly '{"Name":"Test"}' + } + + It 'Should serialize PSCustomObject with multiple properties via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + Name = 'Test' + Value = 42 + Active = $true + } + $expected = '{"Name":"Test","Value":42,"Active":true}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should preserve property order in PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + Zebra = 1 + Alpha = 2 + Middle = 3 + } + $expected = '{"Zebra":1,"Alpha":2,"Middle":3}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with null property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ NullProp = $null } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"NullProp":null}' + $jsonInputObject | Should -BeExactly '{"NullProp":null}' + } + } + + Context 'PSCustomObject with various property types' { + It 'Should serialize PSCustomObject with scalar properties via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + IntVal = 42 + DoubleVal = 3.14 + StringVal = 'hello' + BoolVal = $true + } + $expected = '{"IntVal":42,"DoubleVal":3.14,"StringVal":"hello","BoolVal":true}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with DateTime property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Date = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) + } + $expected = '{"Date":"2024-06-15T10:30:00Z"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with Guid property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Id = [Guid]'12345678-1234-1234-1234-123456789abc' + } + $expected = '{"Id":"12345678-1234-1234-1234-123456789abc"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with enum property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Day = [DayOfWeek]::Monday } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"Day":1}' + $jsonInputObject | Should -BeExactly '{"Day":1}' + } + + It 'Should serialize PSCustomObject with enum as string via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Day = [DayOfWeek]::Monday } + $jsonPipeline = $obj | ConvertTo-Json -Compress -EnumsAsStrings + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -EnumsAsStrings + $jsonPipeline | Should -BeExactly '{"Day":"Monday"}' + $jsonInputObject | Should -BeExactly '{"Day":"Monday"}' + } + + It 'Should serialize PSCustomObject with array property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Numbers = @(1, 2, 3) } + $expected = '{"Numbers":[1,2,3]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with hashtable property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Config = @{ Key = 'Value' } } + $expected = '{"Config":{"Key":"Value"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Nested PSCustomObject' { + It 'Should serialize nested PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Outer = [PSCustomObject]@{ + Inner = 'value' + } + } + $expected = '{"Outer":{"Inner":"value"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize deeply nested PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Level1 = [PSCustomObject]@{ + Level2 = [PSCustomObject]@{ + Level3 = 'deep' + } + } + } + $expected = '{"Level1":{"Level2":{"Level3":"deep"}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested PSCustomObject with Depth limit via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Level1 = [PSCustomObject]@{ + Level2 = [PSCustomObject]@{ + Level3 = 'deep' + } + } + } + $expected = '{"Level1":{"Level2":"@{Level3=deep}"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with mixed nested types via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + Child = [PSCustomObject]@{ Name = 'child' } + Items = @(1, 2, 3) + Config = @{ Key = 'Value' } + } + $expected = '{"Child":{"Name":"child"},"Items":[1,2,3],"Config":{"Key":"Value"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'PSCustomObject ETS properties' { + It 'Should include NoteProperty on PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Original = 'value' } + $obj | Add-Member -MemberType NoteProperty -Name Added -Value 'added' + $expected = '{"Original":"value","Added":"added"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should include ScriptProperty on PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Value = 10 } + $obj | Add-Member -MemberType ScriptProperty -Name Doubled -Value { $this.Value * 2 } + $expected = '{"Value":10,"Doubled":20}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should include multiple ETS properties on PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Base = 'base' } + $obj | Add-Member -MemberType NoteProperty -Name Note1 -Value 'note1' + $obj | Add-Member -MemberType NoteProperty -Name Note2 -Value 'note2' + $expected = '{"Base":"base","Note1":"note1","Note2":"note2"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Array of PSCustomObject' { + It 'Should serialize array of PSCustomObject via Pipeline and InputObject' { + $arr = @( + [PSCustomObject][ordered]@{ Id = 1; Name = 'First' } + [PSCustomObject][ordered]@{ Id = 2; Name = 'Second' } + ) + $expected = '[{"Id":1,"Name":"First"},{"Id":2,"Name":"Second"}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize single PSCustomObject without array wrapper via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Id = 1 } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"Id":1}' + $jsonInputObject | Should -BeExactly '{"Id":1}' + } + + It 'Should serialize single PSCustomObject with -AsArray via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Id = 1 } + $jsonPipeline = $obj | ConvertTo-Json -Compress -AsArray + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -AsArray + $jsonPipeline | Should -BeExactly '[{"Id":1}]' + $jsonInputObject | Should -BeExactly '[{"Id":1}]' + } + } + + #endregion Comprehensive PSCustomObject Tests (Phase 3) + #region Comprehensive Depth Truncation and Multilevel Composition Tests (Phase 4) # Test coverage for ConvertTo-Json depth truncation and complex nested structures # Covers: -Depth parameter behavior, multilevel type compositions From 1965e059bc836046ac0759640f64f9311402c545 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 19 Feb 2026 05:39:53 +1000 Subject: [PATCH 191/255] Fix up default value for parameters with the `in` modifier (#26785) --- .../engine/runtime/Binding/Binders.cs | 7 +++++++ .../engine/Basic/CLRBinding.Tests.ps1 | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 5913c98829b..027c4886380 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -7201,6 +7201,13 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, var argValue = parameters[i].DefaultValue; if (argValue == null) { + if (parameterType.IsByRef) + { + // When the default value is null for a ByRef parameter (e.g. an optional `in` parameter + // using `default`), expression trees cannot create Expression.Default for the T& type. + // In that case we switch to the element type and use Default(TElement) instead. + parameterType = parameterType.GetElementType(); + } argExprs[i] = Expression.Default(parameterType); } else if (!parameters[i].HasDefaultValue && parameterType != typeof(object) && argValue == Type.Missing) diff --git a/test/powershell/engine/Basic/CLRBinding.Tests.ps1 b/test/powershell/engine/Basic/CLRBinding.Tests.ps1 index 98a05d8518e..cb23fa168db 100644 --- a/test/powershell/engine/Basic/CLRBinding.Tests.ps1 +++ b/test/powershell/engine/Basic/CLRBinding.Tests.ps1 @@ -27,6 +27,12 @@ public class TestClass public static string StaticWithOptionalExpected() => StaticWithOptional(); public static string StaticWithOptional([Optional] string value) => value; + public static int PrimitiveTypeWithInDefault(in int value = default) => value; + + public static Guid ValueTypeWithInDefault(in Guid value = default) => value; + + public static string RefTypeWithInDefault(in string value = default) => value; + public object InstanceWithDefaultExpected() => InstanceWithDefault(); public object InstanceWithDefault(object value = null) => value; @@ -101,6 +107,21 @@ public class TestClassCstorWithOptional $actual | Should -Be $expected } + It "Binds to static method with primitive type with in modifier and default argument" { + $actual = [CLRBindingTests.TestClass]::PrimitiveTypeWithInDefault() + $actual | Should -Be 0 + } + + It "Binds to static method with value type with in modifier and default argument" { + $actual = [CLRBindingTests.TestClass]::ValueTypeWithInDefault() + $actual | Should -Be ([Guid]::Empty) + } + + It "Binds to static method with ref type with in modifier and default argument" { + $actual = [CLRBindingTests.TestClass]::RefTypeWithInDefault() + $null -eq $actual | Should -BeTrue + } + It "Binds to instance method with default argument" { $c = [CLRBindingTests.TestClass]::new() From 212c5f97e3bdb0ec175d407b5d471b4af3a3c45b Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 18 Feb 2026 16:35:10 -0800 Subject: [PATCH 192/255] Update to .NET 11 SDK and update dependencies (#26783) --- .pipelines/templates/nupkg.yml | 12 ++++----- .pipelines/templates/windows-hosted-build.yml | 8 +++--- PowerShell.Common.props | 2 +- build.psm1 | 6 ++--- docs/building/linux.md | 2 +- global.json | 2 +- .../PowerShell.Windows.x64.csproj | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 10 ++++---- .../Microsoft.WSMan.Management.csproj | 2 +- src/Modules/PSGalleryModules.csproj | 4 +-- src/ResGen/ResGen.csproj | 2 +- .../System.Management.Automation.csproj | 12 ++++----- .../remoting/common/RemoteSessionNamedPipe.cs | 1 - src/TypeCatalogGen/TypeCatalogGen.csproj | 2 +- src/powershell/Program.cs | 2 +- test/Test.Common.props | 2 +- test/tools/NamedPipeConnection/build.ps1 | 4 +-- ...soft.PowerShell.NamedPipeConnection.csproj | 2 +- test/tools/OpenCover/OpenCover.psm1 | 2 +- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- tools/findMissingNotices.ps1 | 4 +-- tools/packaging/boms/windows.json | 25 +++++++++++-------- tools/packaging/packaging.psm1 | 10 ++++---- tools/packaging/packaging.strings.psd1 | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- .../Microsoft.PowerShell.ConsoleHost.csproj | 2 +- .../System.Management.Automation.csproj | 2 +- 32 files changed, 71 insertions(+), 67 deletions(-) diff --git a/.pipelines/templates/nupkg.yml b/.pipelines/templates/nupkg.yml index 3558c949402..c296aadc242 100644 --- a/.pipelines/templates/nupkg.yml +++ b/.pipelines/templates/nupkg.yml @@ -117,13 +117,13 @@ jobs: Start-PSBuild -Clean -Runtime linux-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar) $sharedModules | Foreach-Object { - $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\$_.dll" + $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net11.0\refint\$_.dll" Write-Verbose -Verbose "RefAssembly: $refFile" Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose - $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml" + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml" if (-not (Test-Path $refDoc)) { Write-Warning "$refDoc not found" - Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0\" | Out-String | Write-Verbose -Verbose + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.0\" | Out-String | Write-Verbose -Verbose } else { Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose @@ -133,13 +133,13 @@ jobs: Start-PSBuild -Clean -Runtime win7-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar) $winOnlyModules | Foreach-Object { - $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\*.dll" + $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net11.0\refint\*.dll" Write-Verbose -Verbose 'RefAssembly: $refFile' Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose - $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml" + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml" if (-not (Test-Path $refDoc)) { Write-Warning "$refDoc not found" - Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0" | Out-String | Write-Verbose -Verbose + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.0" | Out-String | Write-Verbose -Verbose } else { Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose diff --git a/.pipelines/templates/windows-hosted-build.yml b/.pipelines/templates/windows-hosted-build.yml index 83c6b32cdfd..a2933e90817 100644 --- a/.pipelines/templates/windows-hosted-build.yml +++ b/.pipelines/templates/windows-hosted-build.yml @@ -274,7 +274,7 @@ jobs: ) $sourceModulePath = Join-Path '$(GlobalToolArtifactPath)' 'publish' 'PowerShell.Windows.x64' 'release' 'Modules' - $destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' 'modules' + $destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' 'modules' $modulesToCopy | ForEach-Object { $modulePath = Join-Path $sourceModulePath $_ @@ -282,7 +282,7 @@ jobs: } # Copy ref assemblies - Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net10.0\any\ref" -Recurse -Force + Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net11.0\any\ref" -Recurse -Force $contentPath = Join-Path "$outputPath\temp" 'content' $contentFilesPath = Join-Path "$outputPath\temp" 'contentFiles' @@ -290,14 +290,14 @@ jobs: Remove-Item -Path $contentPath,$contentFilesPath -Recurse -Force # remove PDBs to reduce the size of the nupkg - Remove-Item -Path "$outputPath\temp\tools\net10.0\any\*.pdb" -Recurse -Force + Remove-Item -Path "$outputPath\temp\tools\net11.0\any\*.pdb" -Recurse -Force # create powershell.config.json $config = [ordered]@{} $config.Add("Microsoft.PowerShell:ExecutionPolicy", "RemoteSigned") $config.Add("WindowsPowerShellCompatibilityModuleDenyList", @("PSScheduledJob", "BestPractices", "UpdateServices")) - $configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' "powershell.config.json" + $configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' "powershell.config.json" Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop Compress-Archive -Path "$outputPath\temp\*" -DestinationPath "$outputPath\$nupkgName" -Force diff --git a/PowerShell.Common.props b/PowerShell.Common.props index dfc16f830d7..df13f125361 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -144,7 +144,7 @@ (c) Microsoft Corporation. PowerShell 7 - net10.0 + net11.0 13.0 true diff --git a/build.psm1 b/build.psm1 index bf175c5f7c2..ee50c9cde51 100644 --- a/build.psm1 +++ b/build.psm1 @@ -1010,8 +1010,8 @@ function New-PSOptions { [ValidateSet('Debug', 'Release', 'CodeCoverage', 'StaticAnalysis', '')] [string]$Configuration, - [ValidateSet("net10.0")] - [string]$Framework = "net10.0", + [ValidateSet("net11.0")] + [string]$Framework = "net11.0", # These are duplicated from Start-PSBuild # We do not use ValidateScript since we want tab completion @@ -4021,7 +4021,7 @@ function Clear-NativeDependencies $filesToDeleteWinDesktop = @() $deps = Get-Content "$PublishFolder/pwsh.deps.json" -Raw | ConvertFrom-Json -Depth 20 - $targetRuntime = ".NETCoreApp,Version=v10.0/$($script:Options.Runtime)" + $targetRuntime = ".NETCoreApp,Version=v11.0/$($script:Options.Runtime)" $runtimePackNetCore = $deps.targets.${targetRuntime}.PSObject.Properties.Name -like 'runtimepack.Microsoft.NETCore.App.Runtime*' $runtimePackWinDesktop = $deps.targets.${targetRuntime}.PSObject.Properties.Name -like 'runtimepack.Microsoft.WindowsDesktop.App.Runtime*' diff --git a/docs/building/linux.md b/docs/building/linux.md index 6ccf12073e2..55d96e4c21a 100644 --- a/docs/building/linux.md +++ b/docs/building/linux.md @@ -69,7 +69,7 @@ Start-PSBuild -UseNuGetOrg Congratulations! If everything went right, PowerShell is now built. The `Start-PSBuild` script will output the location of the executable: -`./src/powershell-unix/bin/Debug/net10.0/linux-x64/publish/pwsh`. +`./src/powershell-unix/bin/Debug/net11.0/linux-x64/publish/pwsh`. You should now be running the PowerShell Core that you just built, if you run the above executable. You can run our cross-platform Pester tests with `Start-PSPester -UseNuGetOrg`, and our xUnit tests with `Start-PSxUnit`. diff --git a/global.json b/global.json index c2af57a3fe4..2fe6c88b1f6 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.102" + "version": "11.0.100-preview.1.26104.118" } } diff --git a/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj index 49d607ebfed..8449c58ebb0 100644 --- a/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj +++ b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj @@ -2,7 +2,7 @@ Exe - net10.0 + net11.0 enable enable true diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index 32e0d329886..ab9bd210353 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index 7d9d61ede96..a8c2c34aca2 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index eed0722b4d5..56fa91bcf6b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 78232a2f1af..6df470621f5 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 0f827aab553..daa29bd01f3 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,19 +16,19 @@ - - + + - - + + - + diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index da5ea7ddd04..e3bb66c87ac 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index 7008fe6a08d..9136df5c7b3 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -5,7 +5,7 @@ Microsoft Corporation (c) Microsoft Corporation. - net10.0 + net11.0 true @@ -16,7 +16,7 @@ - + diff --git a/src/ResGen/ResGen.csproj b/src/ResGen/ResGen.csproj index 7fcf1ff3f35..954038cfa51 100644 --- a/src/ResGen/ResGen.csproj +++ b/src/ResGen/ResGen.csproj @@ -2,7 +2,7 @@ Generates C# typed bindings for .resx files - net10.0 + net11.0 resgen Exe true diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 8c77c770a8f..d8a66b2ab74 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,12 +32,12 @@ - - - - - - + + + + + + diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index e21608a378f..fc5226a007e 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -1302,7 +1302,6 @@ protected override NamedPipeClientStream DoConnect(int timeout) return new NamedPipeClientStream( PipeDirection.InOut, isAsync: true, - isConnected: true, pipeHandle); } catch (Exception) diff --git a/src/TypeCatalogGen/TypeCatalogGen.csproj b/src/TypeCatalogGen/TypeCatalogGen.csproj index ffc3ff99986..83b21e178f5 100644 --- a/src/TypeCatalogGen/TypeCatalogGen.csproj +++ b/src/TypeCatalogGen/TypeCatalogGen.csproj @@ -2,7 +2,7 @@ Generates CorePsTypeCatalog.cs given powershell.inc - net10.0 + net11.0 true TypeCatalogGen Exe diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index aa79bb8cf07..1de961674df 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -152,7 +152,7 @@ private static void AttemptExecPwshLogin(string[] args) IntPtr executablePathPtr = IntPtr.Zero; try { - mib = [MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid]; + mib = new int[] { MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid }; unsafe { diff --git a/test/Test.Common.props b/test/Test.Common.props index 3fafbbf8f85..be70bda4bcb 100644 --- a/test/Test.Common.props +++ b/test/Test.Common.props @@ -6,7 +6,7 @@ Microsoft Corporation (c) Microsoft Corporation. - net10.0 + net11.0 13.0 true diff --git a/test/tools/NamedPipeConnection/build.ps1 b/test/tools/NamedPipeConnection/build.ps1 index a0978c4cb34..3d92df01fcd 100644 --- a/test/tools/NamedPipeConnection/build.ps1 +++ b/test/tools/NamedPipeConnection/build.ps1 @@ -36,8 +36,8 @@ param ( [ValidateSet("Debug", "Release")] [string] $BuildConfiguration = "Debug", - [ValidateSet("net10.0")] - [string] $BuildFramework = "net10.0" + [ValidateSet("net11.0")] + [string] $BuildFramework = "net11.0" ) $script:ModuleName = 'Microsoft.PowerShell.NamedPipeConnection' diff --git a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj index 89147481bc3..e004a573673 100644 --- a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj +++ b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj @@ -8,7 +8,7 @@ 1.0.0.0 1.0.0 1.0.0 - net10.0 + net11.0 true 13.0 diff --git a/test/tools/OpenCover/OpenCover.psm1 b/test/tools/OpenCover/OpenCover.psm1 index 9e7adb640ca..e8848a15085 100644 --- a/test/tools/OpenCover/OpenCover.psm1 +++ b/test/tools/OpenCover/OpenCover.psm1 @@ -624,7 +624,7 @@ function Invoke-OpenCover [parameter()]$OutputLog = "$HOME/Documents/OpenCover.xml", [parameter()]$TestPath = "${script:psRepoPath}/test/powershell", [parameter()]$OpenCoverPath = "$HOME/OpenCover", - [parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/net10.0/win7-x64/publish", + [parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/net11.0/win7-x64/publish", [parameter()]$PesterLogElevated = "$HOME/Documents/TestResultsElevated.xml", [parameter()]$PesterLogUnelevated = "$HOME/Documents/TestResultsUnelevated.xml", [parameter()]$PesterLogFormat = "NUnitXml", diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index af62ecaacab..5d5f1a9e4f9 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index cbb01a67da5..00cd6825dff 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ - + diff --git a/tools/findMissingNotices.ps1 b/tools/findMissingNotices.ps1 index 42722701d97..1346148baee 100644 --- a/tools/findMissingNotices.ps1 +++ b/tools/findMissingNotices.ps1 @@ -193,8 +193,8 @@ function Get-CGRegistrations { $registrationChanged = $false - $dotnetTargetName = 'net10.0' - $dotnetTargetNameWin7 = 'net10.0-windows8.0' + $dotnetTargetName = 'net11.0' + $dotnetTargetNameWin7 = 'net11.0-windows8.0' $unixProjectName = 'powershell-unix' $windowsProjectName = 'powershell-win-core' $actualRuntime = $Runtime diff --git a/tools/packaging/boms/windows.json b/tools/packaging/boms/windows.json index f811109f818..8248b3d03aa 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -1276,11 +1276,6 @@ "FileType": "NonProduct", "Architecture": null }, - { - "Pattern": "mscorrc.dll", - "FileType": "NonProduct", - "Architecture": null - }, { "Pattern": "msquic.dll", "FileType": "NonProduct", @@ -2496,6 +2491,11 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "ref\\System.IO.Compression.Zstandard.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "ref\\System.IO.Pipelines.dll", "FileType": "NonProduct", @@ -3206,6 +3206,11 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "System.IO.Compression.Zstandard.dll", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "System.IO.dll", "FileType": "NonProduct", @@ -4592,27 +4597,27 @@ "Architecture": null }, { - "Pattern": "RegisterManifest.ps1", + "Pattern": "pwsh.profile.dsc.resource.json", "FileType": "Product", "Architecture": null }, { - "Pattern": "RegisterMicrosoftUpdate.ps1", + "Pattern": "pwsh.profile.resource.ps1", "FileType": "Product", "Architecture": null }, { - "Pattern": "System.Management.Automation.dll", + "Pattern": "RegisterManifest.ps1", "FileType": "Product", "Architecture": null }, { - "Pattern": "pwsh.profile.dsc.resource.json", + "Pattern": "RegisterMicrosoftUpdate.ps1", "FileType": "Product", "Architecture": null }, { - "Pattern": "pwsh.profile.resource.ps1", + "Pattern": "System.Management.Automation.dll", "FileType": "Product", "Architecture": null } diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index cddde65aa9f..ca4d5d46712 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -18,7 +18,7 @@ $AllDistributions = @() $AllDistributions += $DebianDistributions $AllDistributions += $RedhatDistributions $AllDistributions += 'macOs' -$script:netCoreRuntime = 'net10.0' +$script:netCoreRuntime = 'net11.0' $script:iconFileName = "Powershell_black_64.png" $script:iconPath = Join-Path -path $PSScriptRoot -ChildPath "../../assets/$iconFileName" -Resolve @@ -2323,12 +2323,12 @@ function Get-MacOSPackageIdentifierInfo param( [Parameter(Mandatory)] [string]$Version, - + [switch]$LTS ) - + $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS - + # Determine package identifier based on preview status if ($IsPreview) { $PackageIdentifier = 'com.microsoft.powershell-preview' @@ -2336,7 +2336,7 @@ function Get-MacOSPackageIdentifierInfo else { $PackageIdentifier = 'com.microsoft.powershell' } - + return @{ IsPreview = $IsPreview PackageIdentifier = $PackageIdentifier diff --git a/tools/packaging/packaging.strings.psd1 b/tools/packaging/packaging.strings.psd1 index eeb9a86ec10..0bf14ff0dbe 100644 --- a/tools/packaging/packaging.strings.psd1 +++ b/tools/packaging/packaging.strings.psd1 @@ -166,7 +166,7 @@ open {0} - + diff --git a/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index f358b454baa..6a96c8b1173 100644 --- a/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -1,6 +1,6 @@ - net10.0 + net11.0 $(RefAsmVersion) true $(SnkFile) diff --git a/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj b/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj index c0794a6a708..07161449da2 100644 --- a/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj +++ b/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj @@ -1,6 +1,6 @@ - net10.0 + net11.0 $(RefAsmVersion) true $(SnkFile) diff --git a/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj b/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj index f4626e110fd..d633173b9cf 100644 --- a/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj +++ b/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj @@ -1,6 +1,6 @@ - net10.0 + net11.0 $(RefAsmVersion) true $(SnkFile) From 2af5ad14a3d6910600bb8e4905a95fcf8aa86c44 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:39:42 +0000 Subject: [PATCH 193/255] Update `LangVersion` to `preview` (#26214) --- PowerShell.Common.props | 2 +- .../WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs | 4 ++-- .../PSVersionInfoGenerator/PSVersionInfoGenerator.csproj | 2 +- test/Test.Common.props | 2 +- test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj | 2 +- .../src/code/Microsoft.PowerShell.NamedPipeConnection.csproj | 2 +- .../Microsoft.PowerShell.Commands.Utility.csproj | 2 +- .../Microsoft.PowerShell.ConsoleHost.csproj | 2 +- .../System.Management.Automation.csproj | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/PowerShell.Common.props b/PowerShell.Common.props index df13f125361..fa2a5305e32 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -145,7 +145,7 @@ PowerShell 7 net11.0 - 13.0 + preview true true diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs index 9bd76f99413..ea650e80e67 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs @@ -81,9 +81,9 @@ public WebCmdletElementCollection InputFields { List parsedFields = new(); MatchCollection fieldMatch = HtmlParser.InputFieldRegex.Matches(Content); - foreach (Match field in fieldMatch) + foreach (Match match in fieldMatch) { - parsedFields.Add(CreateHtmlObject(field.Value, "INPUT")); + parsedFields.Add(CreateHtmlObject(match.Value, "INPUT")); } _inputFields = new WebCmdletElementCollection(parsedFields); diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj index 1099b2a3c11..44d93a5e31b 100644 --- a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj +++ b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj @@ -7,7 +7,7 @@ netstandard2.0 - 13.0 + preview true true enable diff --git a/test/Test.Common.props b/test/Test.Common.props index be70bda4bcb..8a5522e9eaa 100644 --- a/test/Test.Common.props +++ b/test/Test.Common.props @@ -7,7 +7,7 @@ (c) Microsoft Corporation. net11.0 - 13.0 + preview true true diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index 5cbc66081a3..90143a9f482 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -3,7 +3,7 @@ Exe $(PERFLAB_TARGET_FRAMEWORKS) net5.0 - 13.0 + preview diff --git a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj index e004a573673..ba216dee23e 100644 --- a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj +++ b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj @@ -10,7 +10,7 @@ 1.0.0 net11.0 true - 13.0 + preview diff --git a/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 6a96c8b1173..34ead3a2dc5 100644 --- a/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -5,7 +5,7 @@ true $(SnkFile) true - 13.0 + preview diff --git a/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj b/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj index 07161449da2..378ca59ff95 100644 --- a/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj +++ b/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj @@ -5,7 +5,7 @@ true $(SnkFile) true - 13.0 + preview diff --git a/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj b/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj index d633173b9cf..8f80aa50a3d 100644 --- a/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj +++ b/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj @@ -5,7 +5,7 @@ true $(SnkFile) true - 13.0 + preview From 2f08eb44e69f0cb5d995dfccb541fd07df215d12 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Fri, 20 Feb 2026 04:04:48 +0900 Subject: [PATCH 194/255] Add comprehensive PowerShell class tests for `ConvertTo-Json` (#26769) --- .../ConvertTo-Json.Tests.ps1 | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) 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 8be8631ee6b..f1ddc43a220 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 @@ -1738,4 +1738,156 @@ Describe 'ConvertTo-Json' -tags "CI" { } #endregion Comprehensive Depth Truncation and Multilevel Composition Tests (Phase 4) + + #region Comprehensive PowerShell Class Tests (Phase 5) + # Test coverage for ConvertTo-Json PowerShell class serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, nested structures, inheritance + + Context 'PowerShell class serialization' { + BeforeAll { + class SimpleClass { + [string]$StringVal + [int]$IntVal + [bool]$BoolVal + [double]$DoubleVal + [bigint]$BigIntVal + [guid]$GuidVal + [ipaddress]$IPVal + [object[]]$ArrayVal + [System.Collections.Specialized.OrderedDictionary]$DictVal + hidden [string]$HiddenVal + } + } + + It 'Should serialize PowerShell class with various property types including ETS via Pipeline and InputObject' { + $obj = [SimpleClass]::new() + $obj.StringVal = 'hello' + $obj.IntVal = 42 + $obj.BoolVal = $true + $obj.DoubleVal = 3.14 + $obj.BigIntVal = [bigint]::Parse('99999999999999999999') + $obj.GuidVal = [guid]'12345678-1234-1234-1234-123456789abc' + $obj.IPVal = [ipaddress]::Parse('192.168.1.1') + $obj.ArrayVal = @(1, 'two', $true) + $obj.DictVal = [ordered]@{ Key = 'Value'; Nested = [ordered]@{ Inner = 1 } } + $obj.HiddenVal = 'secret' + $obj | Add-Member -MemberType NoteProperty -Name ETSNote -Value 'note' + $obj | Add-Member -MemberType ScriptProperty -Name ETSScript -Value { $this.StringVal.Length } + $obj.IPVal | Add-Member -MemberType NoteProperty -Name Label -Value 'primary' + $expectedPipeline = '{"StringVal":"hello","IntVal":42,"BoolVal":true,"DoubleVal":3.14,"BigIntVal":99999999999999999999,"GuidVal":"12345678-1234-1234-1234-123456789abc","IPVal":{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952},"ArrayVal":[1,"two",true],"DictVal":{"Key":"Value","Nested":{"Inner":1}},"HiddenVal":"secret","ETSNote":"note","ETSScript":5}' + $expectedInputObject = '{"StringVal":"hello","IntVal":42,"BoolVal":true,"DoubleVal":3.14,"BigIntVal":99999999999999999999,"GuidVal":"12345678-1234-1234-1234-123456789abc","IPVal":{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952},"ArrayVal":[1,"two",true],"DictVal":{"Key":"Value","Nested":{"Inner":1}},"HiddenVal":"secret"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expectedPipeline + $jsonInputObject | Should -BeExactly $expectedInputObject + } + + It 'Should serialize PowerShell class with default values via Pipeline and InputObject' { + $obj = [SimpleClass]::new() + $expected = '{"StringVal":null,"IntVal":0,"BoolVal":false,"DoubleVal":0.0,"BigIntVal":0,"GuidVal":"00000000-0000-0000-0000-000000000000","IPVal":null,"ArrayVal":null,"DictVal":null,"HiddenVal":null}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Nested PowerShell class' { + BeforeAll { + class InnerClass { + [string]$Inner + } + + class OuterClass { + [string]$Outer + [InnerClass]$Child + } + + class DeepClass { + [string]$Name + [OuterClass]$Nested + } + } + + It 'Should serialize deeply nested PowerShell class via Pipeline and InputObject' { + $inner = [InnerClass]@{ Inner = 'deep' } + $outer = [OuterClass]@{ Outer = 'middle'; Child = $inner } + $deep = [DeepClass]@{ Name = 'top'; Nested = $outer } + $expected = '{"Name":"top","Nested":{"Outer":"middle","Child":{"Inner":"deep"}}}' + $jsonPipeline = $deep | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $deep -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested PowerShell class with null child via Pipeline and InputObject' { + $outer = [OuterClass]@{ Outer = 'outer value'; Child = $null } + $expected = '{"Outer":"outer value","Child":null}' + $jsonPipeline = $outer | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $outer -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'PowerShell class inheritance' { + BeforeAll { + class BaseClass { + [string]$BaseProp + } + + class ChildClass : BaseClass { + [string]$ChildProp + } + + class GrandChildClass : ChildClass { + [string]$GrandChildProp + } + } + + It 'Should serialize derived class with base properties via Pipeline and InputObject' { + $obj = [ChildClass]@{ BaseProp = 'base'; ChildProp = 'child' } + $expected = '{"ChildProp":"child","BaseProp":"base"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize multi-level inherited class via Pipeline and InputObject' { + $obj = [GrandChildClass]@{ + BaseProp = 'base' + ChildProp = 'child' + GrandChildProp = 'grandchild' + } + $expected = '{"GrandChildProp":"grandchild","ChildProp":"child","BaseProp":"base"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + } + + Context 'Mixed PSCustomObject and PowerShell class' { + BeforeAll { + class MixedClass { + [string]$ClassName + } + } + + It 'Should serialize array with mixed types via Pipeline and InputObject' { + $classObj = [MixedClass]@{ ClassName = 'class' } + $customObj = [PSCustomObject]@{ CustomName = 'custom' } + $arr = @($classObj, $customObj) + $expected = '[{"ClassName":"class"},{"CustomName":"custom"}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + #endregion Comprehensive PowerShell Class Tests (Phase 5) + } From cf40400cb9b250a65a65fc18064884cb73be10d1 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:28:04 +0200 Subject: [PATCH 195/255] Fetch latest ICU release version dynamically (#26827) --- tools/packaging/packaging.psm1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index ca4d5d46712..f400e060b25 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -2148,7 +2148,7 @@ function Get-PackageDependencies # than the build version and we know that older versions just works. # $MinICUVersion = 60 # runtime minimum supported - $BuildICUVersion = 76 # current build version + $BuildICUVersion = Get-IcuLatestRelease $MaxICUVersion = $BuildICUVersion + 30 # headroom if ($Distribution -eq 'deb') { @@ -5808,3 +5808,15 @@ function Test-IsProductFile { return $false } + +# Get major version from latest ICU release (latest: stable version) +function Get-IcuLatestRelease { + $response = Invoke-WebRequest -Uri "https://github.com/unicode-org/icu/releases/latest" + $tagUrl = ($response.Links | Where-Object href -like "*releases/tag/release-*")[0].href + + if ($tagUrl -match 'release-(\d+)\.') { + return [int]$Matches[1] + } + + throw "Unable to determine the latest ICU release version." +} From 6a91b1f4c7c2c56c555930145f7e72091c3bcc87 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Thu, 19 Feb 2026 20:56:46 -0500 Subject: [PATCH 196/255] Bring the `v7.6.0-rc.1` changelog to master (#26857) --- CHANGELOG/preview.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index f1c5f566289..851640f8cc5 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,5 +1,39 @@ # Preview Changelog +## [7.6.0-rc.1] - 2026-02-19 + +### Tests + +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26705) + +### Build and Packaging Improvements + +
+ + + +

Expand to see details.

+ +
+ +
    +
  • Update branch for release (#26779)
  • +
  • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0-rc3 (#26767)
  • +
  • Update Microsoft.PowerShell.Native package version (#26748)
  • +
  • Move PowerShell build to depend on .NET SDK 10.0.102 (#26717)
  • +
  • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26715)
  • +
  • Fix macOS preview package identifier detection to use version string (#26709)
  • +
  • Update metadata.json to update the Latest attribute with a better name (#26708)
  • +
  • Remove unused runCodesignValidationInjection variable from pipeline templates (#26707)
  • +
  • Update Get-ChangeLog to handle backport PRs correctly (#26706)
  • +
  • Bring release changes from the v7.6.0-preview.6 release (#26626)
  • +
  • Fix the DSC test by skipping AfterAll cleanup if the initial setup in BeforeAll failed (#26622)
  • +
+ +
+ +[7.6.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.6...v7.6.0-rc.1 + ## [7.6.0-preview.6] - 2025-12-11 ### Engine Updates and Fixes From f2c9775d7084192b69f2d5155a33035a44d37596 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Fri, 20 Feb 2026 12:34:59 -0500 Subject: [PATCH 197/255] Update metadata.json for v7.6.0-rc.1 (#26856) --- tools/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/metadata.json b/tools/metadata.json index e0f4952c588..1bf3e96e39c 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,10 +1,10 @@ { "StableReleaseTag": "v7.5.4", - "PreviewReleaseTag": "v7.6.0-preview.5", + "PreviewReleaseTag": "v7.6.0-rc.1", "ServicingReleaseTag": "v7.0.13", "ReleaseTag": "v7.5.4", "LTSReleaseTag" : ["v7.4.13"], - "NextReleaseTag": "v7.6.0-preview.6", + "NextReleaseTag": "v7.7.0-preview.1", "LTSRelease": { "PublishToChannels": false, "Package": false }, "StableRelease": { "PublishToChannels": false, "Package": false } } From b58ec3609342ab8a9bdffe65924e57f77dbcf9c7 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 20 Feb 2026 10:29:13 -0800 Subject: [PATCH 198/255] Exclude .exe packages from publishing to GitHub (#26859) --- .pipelines/templates/release-githubNuget.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pipelines/templates/release-githubNuget.yml b/.pipelines/templates/release-githubNuget.yml index 5f67ce6a9e4..206079c555f 100644 --- a/.pipelines/templates/release-githubNuget.yml +++ b/.pipelines/templates/release-githubNuget.yml @@ -35,6 +35,14 @@ jobs: targetType: inline script: | $Path = "$(Pipeline.Workspace)/GitHubPackages" + + # The .exe packages are for Windows Update only and should not be uploaded to GitHub release. + $exefiles = Get-ChildItem -Path $Path -Filter *.exe + if ($exefiles) { + Write-Verbose -Verbose "Remove .exe packages:" + $exefiles | Remove-Item -Force -Verbose + } + $OutputPath = Join-Path $Path 'hashes.sha256' $packages = Get-ChildItem -Path $Path -Include * -Recurse -File $checksums = $packages | From c19a440244a5b4b4f63b987fb1a816dec77ed2a5 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 20 Feb 2026 14:05:55 -0800 Subject: [PATCH 199/255] Fix `Import-Module.Tests.ps1` to handle Arm32 platform (#26862) --- test/powershell/Language/Scripting/Requires.Tests.ps1 | 2 +- .../Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/powershell/Language/Scripting/Requires.Tests.ps1 b/test/powershell/Language/Scripting/Requires.Tests.ps1 index d4cad910e10..b5cbb397325 100644 --- a/test/powershell/Language/Scripting/Requires.Tests.ps1 +++ b/test/powershell/Language/Scripting/Requires.Tests.ps1 @@ -41,7 +41,7 @@ Describe "Requires tests" -Tags "CI" { BeforeAll { $currentVersion = $PSVersionTable.PSVersion - $powerShellVersions = "1.0", "2.0", "3.0", "4.0", "5.0", "5.1", "6.0", "6.1", "6.2", "7.0", "7.1", "7.2", "7.3", "7.4", "7.5", "7.6" + $powerShellVersions = "1.0", "2.0", "3.0", "4.0", "5.0", "5.1", "6.0", "6.1", "6.2", "7.0", "7.1", "7.2", "7.3", "7.4", "7.5", "7.6", "7.7" $latestVersion = [version]($powerShellVersions | Sort-Object -Descending -Top 1) $nonExistingMinor = "$($latestVersion.Major).$($latestVersion.Minor + 1)" $nonExistingMajor = "$($latestVersion.Major + 1).0" diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 index 6885abe847d..19dc80caf28 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 @@ -62,6 +62,7 @@ Describe "Import-Module" -Tags "CI" { 'X86' { 'x86' } 'X64' { 'amd64' } 'Arm64' { 'arm' } + 'Arm' { 'arm' } default { throw "Unknown processor architecture" } } New-ModuleManifest -Path "$TestDrive\TestModule.psd1" -ProcessorArchitecture $currentProcessorArchitecture From 19fa6a9f79eefebaa8c401b759dcdacc3d4cc703 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:40:05 +0000 Subject: [PATCH 200/255] Remove obsolete `CA2006` rule suppression (#25939) --- .../security/CertificateProvider.cs | 1 - .../engine/remoting/fanin/WSManNativeAPI.cs | 9 --------- .../remoting/fanin/WSManPluginFacade.cs | 10 ---------- .../remoting/fanin/WSManTransportManager.cs | 19 +++---------------- .../security/SecuritySupport.cs | 2 -- 5 files changed, 3 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index 08ee5a70f51..37c687a7770 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -3424,7 +3424,6 @@ internal static IntPtr GetOwnerWindow(PSHost host) return IntPtr.Zero; } #else - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr hWnd = IntPtr.Zero; private static bool firstRun = true; diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs index 289f88c576f..d7bce634620 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs @@ -304,7 +304,6 @@ internal struct WSManUserNameCredentialStruct /// /// Making password secure. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr password; } @@ -626,7 +625,6 @@ internal class WSManBinaryOrTextDataStruct { internal int bufferLength; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr data; } @@ -637,10 +635,8 @@ internal class WSManData_ManToUn : IDisposable { private readonly WSManDataStruct _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _marshalledObject = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _marshalledBuffer = IntPtr.Zero; /// @@ -933,7 +929,6 @@ internal struct WSManStreamIDSetStruct { internal int streamIDsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr streamIDs; } @@ -1085,7 +1080,6 @@ internal struct WSManOptionSetStruct /// /// Pointer to an array of WSManOption objects. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr options; internal bool optionsMustUnderstand; @@ -1223,13 +1217,11 @@ internal struct WSManCommandArgSetInternal { internal int argsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr args; } private WSManCommandArgSetInternal _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private MarshalledObject _data; #region Managed to Unmanaged @@ -1733,7 +1725,6 @@ internal struct WSManShellAsyncCallback // GC handle which prevents garbage collector from collecting this delegate. private GCHandle _gcHandle; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private readonly IntPtr _asyncCallback; internal WSManShellAsyncCallback(WSManShellCompletionFunction callback) diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs index 7fbc236a4dd..2d99a42cfb9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs @@ -343,61 +343,51 @@ internal class WSManPluginEntryDelegatesInternal /// /// WsManPluginShutdownPluginCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownPluginCallbackNative; /// /// WSManPluginShellCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShellCallbackNative; /// /// WSManPluginReleaseShellContextCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseShellContextCallbackNative; /// /// WSManPluginCommandCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginCommandCallbackNative; /// /// WSManPluginReleaseCommandContextCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseCommandContextCallbackNative; /// /// WSManPluginSendCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSendCallbackNative; /// /// WSManPluginReceiveCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReceiveCallbackNative; /// /// WSManPluginSignalCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSignalCallbackNative; /// /// WSManPluginConnectCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginConnectCallbackNative; /// /// WSManPluginCommandCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownCallbackNative; } } diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs index 58b9cfe098d..d4ee779b5a2 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs @@ -317,18 +317,13 @@ internal CompletionEventArgs(CompletionNotification notification) #endregion #region Private Data + // operation handles are owned by WSMan - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSessionHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManShellOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManReceiveOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSendOperationHandle; + // this is used with WSMan callbacks to represent a session transport manager. private long _sessionContextID; @@ -2643,7 +2638,6 @@ private void DisposeWSManAPIDataAsync() /// internal class WSManAPIDataCommon : IDisposable { - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _handle; // if any private WSManNativeApi.WSManStreamIDSet_ManToUn _inputStreamSet; @@ -2791,18 +2785,11 @@ internal sealed class WSManClientCommandTransportManager : BaseClientCommandTran // operation handles private readonly IntPtr _wsManShellOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManCmdOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _cmdSignalOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManReceiveOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSendOperationHandle; + // this is used with WSMan callbacks to represent a command transport manager. private long _cmdContextId; diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 12e03d85174..dc6d048c5b1 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -1607,10 +1607,8 @@ internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) } } - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiContext = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiSession = IntPtr.Zero; private static readonly bool s_amsiInitFailed = false; From cfd59665308d440795c6430d9ae0f5432211e609 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:11:12 +0000 Subject: [PATCH 201/255] Enable CA1852: Seal internal types (#25890) --- .globalconfig | 4 ++++ test/tools/TestAlc/init/Init.cs | 2 +- test/tools/TestExe/TestExe.cs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.globalconfig b/.globalconfig index 21ecbc766aa..e0dd4ccb9e5 100644 --- a/.globalconfig +++ b/.globalconfig @@ -510,6 +510,10 @@ dotnet_diagnostic.CA1846.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847 dotnet_diagnostic.CA1847.severity = warning +# CA1852: Seal internal types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852 +dotnet_diagnostic.CA1852.severity = warning + # CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1853 dotnet_diagnostic.CA1853.severity = warning diff --git a/test/tools/TestAlc/init/Init.cs b/test/tools/TestAlc/init/Init.cs index 4241e56fa4e..33f6635f712 100644 --- a/test/tools/TestAlc/init/Init.cs +++ b/test/tools/TestAlc/init/Init.cs @@ -10,7 +10,7 @@ namespace Test.Isolated.Init { - internal class CustomLoadContext : AssemblyLoadContext + internal sealed class CustomLoadContext : AssemblyLoadContext { private readonly string _dependencyDirPath; diff --git a/test/tools/TestExe/TestExe.cs b/test/tools/TestExe/TestExe.cs index a9b3d834261..9230f9e6bff 100644 --- a/test/tools/TestExe/TestExe.cs +++ b/test/tools/TestExe/TestExe.cs @@ -20,7 +20,7 @@ internal enum EnvTarget System = 2, } - internal class TestExe + internal sealed class TestExe { private static int Main(string[] args) { From 7ca2b72af765aa52ebde99aa1214f6ec29f51868 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 23 Feb 2026 12:33:12 -0800 Subject: [PATCH 202/255] Fix a preview detection test for the packaging script (#26882) --- test/packaging/packaging.tests.ps1 | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/packaging/packaging.tests.ps1 b/test/packaging/packaging.tests.ps1 index cc4bbe0d728..a7d322205bc 100644 --- a/test/packaging/packaging.tests.ps1 +++ b/test/packaging/packaging.tests.ps1 @@ -46,18 +46,15 @@ Describe "Packaging Module Functions" { $result.PackageIdentifier | Should -Be "com.microsoft.powershell" } - It "Should NOT use package name for preview detection (bug fix verification)" { + It "Should NOT use package name for preview detection (bug fix verification) - " -TestCases @( + @{ Version = "7.6.0-preview.6"; Name = "Preview" } + @{ Version = "7.6.0-rc.1"; Name = "RC" } + ) { # This test verifies the fix for issue #26673 # The bug was using ($Name -like '*-preview') which always returned false # because preview builds use Name="powershell" not "powershell-preview" - - $Version = "7.6.0-preview.6" - $Name = "powershell" # Preview builds use "powershell" not "powershell-preview" - - # The INCORRECT logic (the bug): $Name -like '*-preview' - $incorrectCheck = $Name -like '*-preview' - $incorrectCheck | Should -Be $false -Because "Package name is 'powershell' not 'powershell-preview'" - + param($Version) + # The CORRECT logic (the fix): uses version string $result = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$false $result.IsPreview | Should -Be $true -Because "Version string correctly identifies preview" From f3eb51b1f40c9b731dfcf46284f51a646973c6c8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:35:13 -0800 Subject: [PATCH 203/255] Add GitOps policy to auto-label backport candidates when CL-BuildPackaging is added (#26881) --- ...d.clBuildPackaging.addBackportConsider.yml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml diff --git a/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml b/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml new file mode 100644 index 00000000000..78edc18cb1a --- /dev/null +++ b/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml @@ -0,0 +1,56 @@ +id: labelAdded.clBuildPackaging.addBackportConsider +name: GitOps.PullRequestIssueManagement +description: Add backport consideration labels when CL-BuildPackaging is added to an open PR targeting master +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Add BackPort-7.4.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.4.x-Consider + then: + - addLabel: + label: BackPort-7.4.x-Consider + + - description: Add BackPort-7.5.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.5.x-Consider + then: + - addLabel: + label: BackPort-7.5.x-Consider + + - description: Add BackPort-7.6.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.6.x-Consider + then: + - addLabel: + label: BackPort-7.6.x-Consider +onFailure: +onSuccess: From 7cdcd5893f784ddee1e6db702013190e83c0bb7a Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 23 Feb 2026 13:45:32 -0800 Subject: [PATCH 204/255] Correct the package name for .deb and .rpm packages (#26877) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Travis Plunk --- .github/workflows/macos-ci.yml | 3 ++- tools/packaging/packaging.psm1 | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 8f15a2f3a6d..c7717a26738 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -54,6 +54,7 @@ jobs: outputs: source: ${{ steps.filter.outputs.source }} buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout uses: actions/checkout@v6 @@ -161,7 +162,7 @@ jobs: name: macOS packaging and testing needs: - changes - if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} runs-on: - macos-15-large steps: diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index f400e060b25..16ae6b21fcc 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1159,12 +1159,16 @@ function New-UnixPackage { } # Determine if the version is a preview version - # Only LTS packages get a prefix in the name - # Preview versions are identified by the version string itself (e.g., 7.6.0-preview.6) - # Rebuild versions are also identified by the version string (e.g., 7.4.13-rebuild.5) + $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS + + # For deb/rpm packages, use the '-lts' and '-preview' channel suffix variants to match existing names on packages.microsoft.com. + # For osxpkg package, only LTS packages get a channel suffix in the name. $Name = if($LTS) { "powershell-lts" } + elseif ($IsPreview -and $Type -ne "osxpkg") { + "powershell-preview" + } else { "powershell" } From 43d4388d038a663bbd500481d3fd52817c00b93e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:27:47 -0800 Subject: [PATCH 205/255] Bump github/codeql-action from 4.32.3 to 4.32.4 (#26879) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 96fb89287d6..fbbb5c31efe 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5 + uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5 + uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 422381cf50c..c51e85b9448 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5 + uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 with: sarif_file: results.sarif From a6e0d94c7c002618c10f14b9c70addfc2f7cf8f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:57:39 -0800 Subject: [PATCH 206/255] Bump actions/dependency-review-action from 4.8.2 to 4.8.3 (#26861) --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index dbef2e1c8b2..6bace648853 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3 From 7311378cf9bdb8567becaecdcad49315da00175b Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:59:25 -0700 Subject: [PATCH 207/255] Add version in description and pass store task on failure (#26885) --- .pipelines/templates/release-MSIX-Publish.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml index 2bf1e130103..a92c71f826b 100644 --- a/.pipelines/templates/release-MSIX-Publish.yml +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -59,8 +59,15 @@ jobs: $json.listings.'en-us'.baseListing.releaseNotes = $message + # Add PowerShell version to the top of the description + $description = $json.listings.'en-us'.baseListing.description + $version = "$(ReleaseTag)" + $updatedDescription = "Version: $version`n`n$description" + $json.listings.'en-us'.baseListing.description = $updatedDescription + Write-Verbose -Verbose "Updated description: $updatedDescription" + $json | ConvertTo-Json -Depth 100 | Set-Content $jsonPath -Encoding UTF8 - displayName: 'Update Release Notes in JSON' + displayName: 'Add Changelog Link and Version Number to SBJSON' - task: PowerShell@2 inputs: @@ -101,6 +108,7 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 displayName: 'Publish StoreBroker Package (Stable/LTS)' condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true'))) + continueOnError: true inputs: serviceEndpoint: 'StoreAppPublish-Stable' appId: '$(AppID)' @@ -114,6 +122,7 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 displayName: 'Publish StoreBroker Package (Preview)' condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['PREVIEW'], 'true')) + continueOnError: true inputs: serviceEndpoint: 'StoreAppPublish-Preview' appId: '$(AppID)' From 6e2b4c784a8b2e832e5442c9b2c72f3433339873 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 25 Feb 2026 14:51:56 -0500 Subject: [PATCH 208/255] Split TPN manifest and Component Governance manifest (#26891) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../templates/compliance/generateNotice.yml | 2 +- .vsts-ci/linux-internal.yml | 2 +- .vsts-ci/mac.yml | 2 +- .vsts-ci/psresourceget-acr.yml | 2 +- .vsts-ci/windows-arm64.yml | 2 +- tools/{ => cgmanifest/main}/cgmanifest.json | 96 +-- tools/cgmanifest/tpn/cgmanifest.json | 755 ++++++++++++++++++ tools/clearlyDefined/ClearlyDefined.ps1 | 2 +- .../Find-LastHarvestedVersion.ps1 | 156 ++++ .../src/ClearlyDefined/ClearlyDefined.psm1 | 275 ++++++- tools/findMissingNotices.ps1 | 234 +++++- tools/packaging/packaging.psm1 | 2 +- 12 files changed, 1459 insertions(+), 71 deletions(-) rename tools/{ => cgmanifest/main}/cgmanifest.json (91%) create mode 100644 tools/cgmanifest/tpn/cgmanifest.json create mode 100644 tools/clearlyDefined/Find-LastHarvestedVersion.ps1 diff --git a/.pipelines/templates/compliance/generateNotice.yml b/.pipelines/templates/compliance/generateNotice.yml index 0a38ed8f8e6..90fd08dd8d9 100644 --- a/.pipelines/templates/compliance/generateNotice.yml +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -55,7 +55,7 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(repoRoot)\tools' + sourceScanPath: '$(repoRoot)\tools\cgmanifest\tpn' - pwsh: | $(repoRoot)/tools/clearlyDefined/ClearlyDefined.ps1 -TestAndHarvest diff --git a/.vsts-ci/linux-internal.yml b/.vsts-ci/linux-internal.yml index c1c8bcef62d..b90ab0d9eb4 100644 --- a/.vsts-ci/linux-internal.yml +++ b/.vsts-ci/linux-internal.yml @@ -34,7 +34,7 @@ pr: - .vsts-ci/misc-analysis.yml - .vsts-ci/windows.yml - .vsts-ci/windows/* - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml index 4d3681edca1..678ded65259 100644 --- a/.vsts-ci/mac.yml +++ b/.vsts-ci/mac.yml @@ -34,7 +34,7 @@ pr: - .vsts-ci/misc-analysis.yml - .vsts-ci/windows.yml - .vsts-ci/windows/* - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* diff --git a/.vsts-ci/psresourceget-acr.yml b/.vsts-ci/psresourceget-acr.yml index 194c7ba9f57..225e2699533 100644 --- a/.vsts-ci/psresourceget-acr.yml +++ b/.vsts-ci/psresourceget-acr.yml @@ -34,7 +34,7 @@ pr: - .github/ISSUE_TEMPLATE/* - .github/workflows/* - .vsts-ci/misc-analysis.yml - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* diff --git a/.vsts-ci/windows-arm64.yml b/.vsts-ci/windows-arm64.yml index 4c75c1d31e0..1c4bc2ee8af 100644 --- a/.vsts-ci/windows-arm64.yml +++ b/.vsts-ci/windows-arm64.yml @@ -28,7 +28,7 @@ pr: - .dependabot/config.yml - .github/ISSUE_TEMPLATE/* - .vsts-ci/misc-analysis.yml - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* diff --git a/tools/cgmanifest.json b/tools/cgmanifest/main/cgmanifest.json similarity index 91% rename from tools/cgmanifest.json rename to tools/cgmanifest/main/cgmanifest.json index 2334f070852..a0746028a56 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest/main/cgmanifest.json @@ -85,7 +85,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -125,7 +125,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -165,7 +165,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -175,7 +175,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -185,7 +185,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -205,7 +205,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -215,7 +215,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -225,7 +225,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -235,7 +235,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -245,7 +245,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -255,7 +255,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -265,7 +265,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -275,7 +275,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -285,7 +285,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -295,7 +295,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -305,7 +305,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -315,7 +315,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -325,7 +325,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -335,7 +335,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -355,7 +355,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -365,7 +365,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -375,7 +375,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -435,7 +435,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -445,7 +445,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -455,7 +455,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -465,7 +465,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -475,7 +475,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -485,7 +485,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -505,7 +505,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -515,7 +515,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -525,7 +525,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -535,7 +535,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -545,7 +545,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -555,7 +555,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -565,7 +565,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -575,7 +575,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -585,7 +585,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -595,7 +595,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -605,7 +605,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -615,7 +615,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -625,7 +625,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -635,7 +635,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -645,7 +645,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -655,7 +655,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -705,7 +705,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -715,7 +715,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -725,7 +725,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -745,7 +745,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "10.0.2" + "Version": "10.0.3" } }, "DevelopmentDependency": false diff --git a/tools/cgmanifest/tpn/cgmanifest.json b/tools/cgmanifest/tpn/cgmanifest.json new file mode 100644 index 00000000000..a0746028a56 --- /dev/null +++ b/tools/cgmanifest/tpn/cgmanifest.json @@ -0,0 +1,755 @@ +{ + "Registrations": [ + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "DotNetAnalyzers.DocumentationAnalyzers.Unstable", + "Version": "1.0.0.59" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "DotNetAnalyzers.DocumentationAnalyzers", + "Version": "1.0.0-beta.59" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Humanizer.Core", + "Version": "2.14.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Json.More.Net", + "Version": "2.1.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "JsonPointer.Net", + "Version": "5.3.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "JsonSchema.Net", + "Version": "7.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Markdig.Signed", + "Version": "0.45.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.ApplicationInsights", + "Version": "2.23.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Bcl.AsyncInterfaces", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.CodeAnalysis.Analyzers", + "Version": "3.11.0" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.CodeAnalysis.Common", + "Version": "5.0.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.CodeAnalysis.CSharp", + "Version": "5.0.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Extensions.ObjectPool", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Management.Infrastructure.Runtime.Win", + "Version": "3.0.0" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.PowerShell.MarkdownRender", + "Version": "7.2.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Security.Extensions", + "Version": "1.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Win32.Registry.AccessControl", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Win32.SystemEvents", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Windows.Compatibility", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Newtonsoft.Json", + "Version": "13.0.4" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-arm.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-x86.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.win-x64.runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.win-x86.runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "StyleCop.Analyzers.Unstable", + "Version": "1.2.0.556" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "StyleCop.Analyzers", + "Version": "1.1.118" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.CodeDom", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ComponentModel.Composition.Registration", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ComponentModel.Composition", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Configuration.ConfigurationManager", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.Odbc", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.OleDb", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.SqlClient", + "Version": "4.9.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Diagnostics.EventLog", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Diagnostics.PerformanceCounter", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices.AccountManagement", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices.Protocols", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Drawing.Common", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.IO.Packaging", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Management", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Net.Http.WinHttpHandler", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Reflection.Context", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Runtime.Caching", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.Pkcs", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.ProtectedData", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.Xml", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Permissions", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.Http", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.NetFramingBase", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.NetTcp", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.Primitives", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.Syndication", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceProcess.ServiceController", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Speech", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Web.Services.Description", + "Version": "8.1.2" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Windows.Extensions", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + } + ], + "$schema": "https://json.schemastore.org/component-detection-manifest.json" +} diff --git a/tools/clearlyDefined/ClearlyDefined.ps1 b/tools/clearlyDefined/ClearlyDefined.ps1 index 1830c2969e5..c5303b8622b 100644 --- a/tools/clearlyDefined/ClearlyDefined.ps1 +++ b/tools/clearlyDefined/ClearlyDefined.ps1 @@ -21,7 +21,7 @@ if ($ForceModuleReload) { Import-Module -Name "$PSScriptRoot/src/ClearlyDefined" @extraParams -$cgManfest = Get-Content "$PSScriptRoot/../cgmanifest.json" | ConvertFrom-Json +$cgManfest = Get-Content "$PSScriptRoot/../cgmanifest/main/cgmanifest.json" | ConvertFrom-Json $fullCgList = $cgManfest.Registrations.Component | ForEach-Object { [Pscustomobject]@{ diff --git a/tools/clearlyDefined/Find-LastHarvestedVersion.ps1 b/tools/clearlyDefined/Find-LastHarvestedVersion.ps1 new file mode 100644 index 00000000000..a989a3e1fc4 --- /dev/null +++ b/tools/clearlyDefined/Find-LastHarvestedVersion.ps1 @@ -0,0 +1,156 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Find the last harvested version of a NuGet package from ClearlyDefined. + +.DESCRIPTION + Searches for the last harvested version of a package by checking versions + backwards from the specified current version. This is useful for reverting + to a known-good harvested version when a newer version hasn't been harvested yet. + +.PARAMETER Name + The NuGet package name to search for. + +.PARAMETER CurrentVersion + The version to start searching backwards from. Version comparison uses semantic versioning. + +.PARAMETER PackageSourceName + The NuGet package source name to use when searching for available versions. + Default is 'findMissingNoticesNugetOrg' if not specified. + +.EXAMPLE + Find-LastHarvestedVersion -Name "Microsoft.Windows.Compatibility" -CurrentVersion "8.0.24" + + # This will return "8.0.22" if that's the last harvested version + +.NOTES + Requires the ClearlyDefined module to be imported: + Import-Module ".\clearlyDefined\src\ClearlyDefined" -Force +#> + +function Find-LastHarvestedVersion { + [CmdletBinding()] + param( + [parameter(Mandatory)] + [string]$Name, + + [parameter(Mandatory)] + [string]$CurrentVersion, + + [string]$PackageSourceName = 'findMissingNoticesNugetOrg' + ) + + try { + Write-Verbose "Finding last harvested version for $Name starting from v$CurrentVersion..." + + # Parse the current version + try { + [System.Management.Automation.SemanticVersion]$currentSemVer = $CurrentVersion + } catch { + [Version]$currentSemVer = $CurrentVersion + } + + # First try the ClearlyDefined search API (more efficient) + try { + Write-Verbose "Searching ClearlyDefined API for versions of $Name (sorted by release date)..." + # Get versions sorted by release date descending (newest first) for efficiency + $versions = Get-ClearlyDefinedPackageVersions -PackageName $Name + + if ($versions -and $versions.Count -gt 0) { + # Results are already sorted by release date newest first + # Filter to versions <= current version + foreach ($versionInfo in $versions) { + try { + $versionObj = [System.Management.Automation.SemanticVersion]$versionInfo.Version + if ($versionObj -le $currentSemVer) { + # Check harvest status + if ($versionInfo.Harvested) { + Write-Verbose "Found harvested version: v$($versionInfo.Version)" + return $versionInfo.Version + } else { + Write-Verbose "v$($versionInfo.Version) - Not harvested, continuing..." + } + } + } catch { + # Skip versions that can't be parsed + } + } + + Write-Verbose "No harvested version found in ClearlyDefined results" + return $null + } + } catch { + Write-Verbose "ClearlyDefined search API failed ($_), falling back to NuGet search..." + } + + # Fallback: Get all available versions from NuGet and check individually + Write-Verbose "Falling back to NuGet source search..." + + # Ensure package source exists + if (!(Get-PackageSource -Name $PackageSourceName -ErrorAction SilentlyContinue)) { + Write-Verbose "Registering package source: $PackageSourceName" + $null = Register-PackageSource -Name $PackageSourceName -Location https://www.nuget.org/api/v2 -ProviderName NuGet + } + + # Get all available versions from NuGet + try { + $allVersions = Find-Package -Name $Name -AllowPrereleaseVersions -source $PackageSourceName -AllVersions -ErrorAction SilentlyContinue | ForEach-Object { + try { + $packageVersion = [System.Management.Automation.SemanticVersion]$_.Version + } catch { + $packageVersion = [Version]$_.Version + } + $_ | Add-Member -Name SemVer -MemberType NoteProperty -Value $packageVersion -PassThru + } | Where-Object { $_.SemVer -le $currentSemVer } | Sort-Object -Property SemVer -Descending | ForEach-Object { $_.Version } + } catch { + Write-Warning "Failed to get versions for $Name : $_" + return $null + } + + if (!$allVersions) { + Write-Verbose "No versions found for $Name" + return $null + } + + # Check each version backwards until we find one that's harvested + foreach ($version in $allVersions) { + $pkg = [PSCustomObject]@{ + type = "nuget" + Name = $Name + PackageVersion = $version + } + + try { + $result = $pkg | Get-ClearlyDefinedData + if ($result -and $result.harvested) { + Write-Verbose "Found harvested version: v$version" + return $version + } else { + Write-Verbose "v$version - Not harvested, continuing..." + } + } catch { + Write-Verbose "Error checking v$version : $_" -Verbose + } + } + + Write-Verbose "No harvested version found for $Name" + return $null + } finally { + Save-ClearlyDefinedCache + } +} + +# If this script is called directly (not sourced), run a test +if ($MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -like '. "*Find-LastHarvestedVersion*') { + # Script was sourced, just load the function +} else { + # Script was called directly + Write-Host "Testing Find-LastHarvestedVersion function..." + Write-Host "Ensure ClearlyDefined module is loaded first:" + Write-Host ' Import-Module ".\clearlydefined\src\ClearlyDefined" -Force' + Write-Host "" + Write-Host "Example usage:" + Write-Host ' Find-LastHarvestedVersion -Name "Microsoft.Windows.Compatibility" -CurrentVersion "8.0.24"' +} diff --git a/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 b/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 index 4d874402977..4e3d375dc5a 100644 --- a/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 +++ b/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 @@ -2,6 +2,9 @@ # Licensed under the MIT License. # Start the collection (known as harvest) of ClearlyDefined data for a package + +$retryIntervalSec = 90 +$maxRetryCount = 5 function Start-ClearlyDefinedHarvest { [CmdletBinding()] param( @@ -27,7 +30,9 @@ function Start-ClearlyDefinedHarvest { $coordinates = Get-ClearlyDefinedCoordinates @PSBoundParameters $body = @{tool='package';coordinates=$coordinates} | convertto-json Write-Verbose $body -Verbose - (Invoke-WebRequest -Method Post -Uri 'https://api.clearlydefined.io/harvest' -Body $body -ContentType 'application/json' -MaximumRetryCount 5 -RetryIntervalSec 60 -Verbose).Content + Start-job -ScriptBlock { + Invoke-WebRequest -Method Post -Uri 'https://api.clearlydefined.io/harvest' -Body $using:body -ContentType 'application/json' -MaximumRetryCount $using:maxRetryCount -RetryIntervalSec $using:retryIntervalSec + } } } @@ -74,6 +79,207 @@ Function Get-ClearlyDefinedCoordinates { # Cache of ClearlyDefined data $cdCache = @{} +function Test-ClearlyDefinedCachePersistenceAllowed { + [CmdletBinding()] + param() + + if ($env:TF_BUILD -or $env:ADO_BUILD_ID -or $env:BUILD_BUILDID) { + return $false + } + + if ($env:GITHUB_ACTIONS -or $env:GITHUB_RUN_ID) { + return $false + } + + return $true +} + +function Get-ClearlyDefinedCachePath { + [CmdletBinding()] + param() + + $tempPath = [System.IO.Path]::GetTempPath() + return (Join-Path -Path $tempPath -ChildPath 'clearlydefined-cache.json') +} + +function Save-ClearlyDefinedCache { + [CmdletBinding()] + param() + + if (-not (Test-ClearlyDefinedCachePersistenceAllowed)) { + Write-Verbose 'Skipping cache persistence for CI environment.' + return + } + + if ($cdCache.Count -eq 0) { + Write-Verbose 'No cache entries to persist.' + return + } + + $cachePath = Get-ClearlyDefinedCachePath + $entries = foreach ($key in $cdCache.Keys) { + [PSCustomObject]@{ + coordinates = $key + data = $cdCache[$key] + } + } + + $cachePayload = @{ + savedAtUtc = (Get-Date).ToUniversalTime() + entries = $entries + } | ConvertTo-Json -Depth 20 + + $cachePayload | Set-Content -Path $cachePath -Encoding UTF8 + Write-Verbose "Persisted cache to $cachePath" +} + +function Import-ClearlyDefinedCache { + [CmdletBinding()] + param() + + if (-not (Test-ClearlyDefinedCachePersistenceAllowed)) { + Write-Verbose 'Skipping cache import for CI environment.' + return + } + + $cachePath = Get-ClearlyDefinedCachePath + if (-not (Test-Path -Path $cachePath)) { + Write-Verbose 'No persisted cache found.' + return + } + + try { + $payload = Get-Content -Path $cachePath -Raw | ConvertFrom-Json + } catch { + Write-Verbose "Failed to read cache file: $cachePath" + return + } + + if (-not $payload.entries) { + Write-Verbose 'Cache file did not contain entries.' + return + } + + foreach ($entry in $payload.entries) { + if (-not $entry.coordinates -or -not $entry.data) { + continue + } + + try { + $entry.data.cachedTime = [datetime]$entry.data.cachedTime + } catch { + continue + } + + $cdCache[$entry.coordinates] = $entry.data + } + + Write-Verbose "Imported $($cdCache.Count) cache entries from $cachePath" +} + +# Search for packages in ClearlyDefined +Function Search-ClearlyDefined { + [CmdletBinding()] + param( + [string]$Type = 'nuget', + [string]$Provider = 'nuget', + [string]$Namespace, + [string]$Name, + [string]$Pattern, + [datetime]$ReleasedAfter, + [datetime]$ReleasedBefore, + [ValidateSet('releaseDate', 'name')] + [string]$Sort, + [switch]$SortDesc + ) + + $queryParams = @() + if ($Type) { $queryParams += "type=$([System.Uri]::EscapeDataString($Type))" } + if ($Provider) { $queryParams += "provider=$([System.Uri]::EscapeDataString($Provider))" } + if ($Namespace) { $queryParams += "namespace=$([System.Uri]::EscapeDataString($Namespace))" } + if ($Name) { $queryParams += "name=$([System.Uri]::EscapeDataString($Name))" } + if ($Pattern) { $queryParams += "pattern=$([System.Uri]::EscapeDataString($Pattern))" } + if ($ReleasedAfter) { $queryParams += "releasedAfter=$($ReleasedAfter.ToString('o'))" } + if ($ReleasedBefore) { $queryParams += "releasedBefore=$($ReleasedBefore.ToString('o'))" } + if ($Sort) { $queryParams += "sort=$([System.Uri]::EscapeDataString($Sort))" } + if ($SortDesc) { $queryParams += "sortDesc=true" } + + $searchUri = "https://api.clearlydefined.io/definitions?" + ($queryParams -join '&') + Write-Verbose "Searching ClearlyDefined: $searchUri" + + try { + $results = Invoke-RestMethod -Uri $searchUri -MaximumRetryCount $maxRetryCount -RetryIntervalSec $retryIntervalSec + return $results + } catch { + if ($retryIntervalSec -lt 300) { + $retryIntervalSec++ + } + + Write-Warning "Failed to search ClearlyDefined: $_" + return $null + } +} + +# Get available versions for a NuGet package with harvest status +Function Get-ClearlyDefinedPackageVersions { + [CmdletBinding()] + param( + [parameter(mandatory = $true)] + [string] + $PackageName, + + [validateset('nuget')] + [string] + $PackageType = 'nuget' + ) + + # Search for all definitions of this package, sorted by release date (newest first) + Write-Verbose "Fetching versions of $PackageName from ClearlyDefined..." + + $results = Search-ClearlyDefined -Type $PackageType -Provider nuget -Name $PackageName -Sort releaseDate -SortDesc + + if (!$results) { + Write-Verbose "No results found for $PackageName" + return @() + } + + # Convert results to version info objects + $versions = @() + + # API returns results in different formats depending on the query + $dataArray = $null + if ($results.data) { + $dataArray = $results.data + } elseif ($results -is [array]) { + $dataArray = $results + } elseif ($results.PSObject.Properties.Count -gt 0) { + # If it's an object with properties, try to extract the actual results + foreach ($prop in $results.PSObject.Properties) { + if ($prop.Value -is [object] -and $prop.Value.revision) { + $dataArray += $prop.Value + } + } + } + + if ($dataArray) { + foreach ($item in $dataArray) { + if ($item.revision) { + $harvested = if ($item.licensed -and $item.licensed.declared) { $true } else { $false } + + $versions += [PSCustomObject]@{ + Name = $item.name + Version = $item.revision + Harvested = $harvested + Licensed = $item.licensed.declared + } + } + } + } + + # Results are already sorted by API, no need to re-sort + return $versions +} + # Get the ClearlyDefined data for a package Function Get-ClearlyDefinedData { [CmdletBinding()] @@ -96,8 +302,9 @@ Function Get-ClearlyDefinedData { ) Begin { - $cacheMinutes = 60 - $cacheCutoff = (get-date).AddMinutes(-$cacheMinutes) + # Different TTLs for different cache types + $harvestedCacheMinutes = 60 # Cache positive results for 60 minutes + $nonHarvestedCacheMinutes = 30 # Cache negative results for 30 minutes (less aggressive) $coordinateList = @() } @@ -111,19 +318,55 @@ Function Get-ClearlyDefinedData { foreach($coordinates in $coordinateList) { Write-Progress -Activity "Getting ClearlyDefined data" -Status "Getting data for $coordinates" -PercentComplete (($completed / $total) * 100) $containsKey = $cdCache.ContainsKey($coordinates) - if ($containsKey -and $cdCache[$coordinates].cachedTime -gt $cacheCutoff) { - Write-Verbose "Returning cached data for $coordinates" - Write-Output $cdCache[$coordinates] - continue + + if ($containsKey) { + $cached = $cdCache[$coordinates] + # Check if cache entry is still valid based on its type + $cacheCutoff = if ($cached.harvestedResult) { + (get-date).AddMinutes(-$harvestedCacheMinutes) + } else { + (get-date).AddMinutes(-$nonHarvestedCacheMinutes) + } + + if ($cached.cachedTime -gt $cacheCutoff) { + Write-Progress -Activity "Getting ClearlyDefined data" -Status "Getting data for $coordinates - cache hit" -PercentComplete (($completed / $total) * 100) + Write-Verbose "Returning cached data for $coordinates (harvested: $($cached.harvestedResult))" + Write-Output $cached + $completed++ + continue + } } - Invoke-RestMethod -Uri "https://api.clearlydefined.io/definitions/$coordinates" -MaximumRetryCount 5 -RetryIntervalSec 60 | ForEach-Object { - [bool] $harvested = if ($_.licensed.declared) { $true } else { $false } - Add-Member -NotePropertyName cachedTime -NotePropertyValue (get-date) -InputObject $_ -PassThru | Add-Member -NotePropertyName harvested -NotePropertyValue $harvested -PassThru - if ($_.harvested) { - Write-Verbose "Caching data for $coordinates" - $cdCache[$coordinates] = $_ + Write-Progress -Activity "Getting ClearlyDefined data" -Status "Getting data for $coordinates - cache miss" -PercentComplete (($completed / $total) * 100) + + try { + Invoke-RestMethod -Uri "https://api.clearlydefined.io/definitions/$coordinates" -MaximumRetryCount $maxRetryCount -RetryIntervalSec $retryIntervalSec | ForEach-Object { + [bool] $harvested = if ($_.licensed.declared) { $true } else { $false } + # Always cache, with harvestedResult property to distinguish for TTL purposes + Add-Member -NotePropertyName cachedTime -NotePropertyValue (get-date) -InputObject $_ -PassThru | + Add-Member -NotePropertyName harvested -NotePropertyValue $harvested -PassThru | + Add-Member -NotePropertyName harvestedResult -NotePropertyValue $harvested -PassThru | + ForEach-Object { + Write-Verbose "Caching data for $coordinates (harvested: $($_.harvested))" + $cdCache[$coordinates] = $_ + Write-Output $_ + } + } + } catch { + if ($retryIntervalSec -lt 300) { + $retryIntervalSec++ + } + + Write-Warning "Failed to get ClearlyDefined data for $coordinates : $_" + # Return a minimal object indicating failure/not harvested so the pipeline continues + $failedResult = [PSCustomObject]@{ + coordinates = $coordinates + harvested = $false + harvestedResult = $false + cachedTime = (get-date) + licensed = @{ declared = $null } } + Write-Output $failedResult } $completed++ } @@ -134,4 +377,10 @@ Export-ModuleMember -Function @( 'Start-ClearlyDefinedHarvest' 'Get-ClearlyDefinedData' 'ConvertFrom-ClearlyDefinedCoordinates' + 'Search-ClearlyDefined' + 'Get-ClearlyDefinedPackageVersions' + 'Save-ClearlyDefinedCache' + 'Import-ClearlyDefinedCache' + 'Test-ClearlyDefinedCachePersistenceAllowed' + 'Get-ClearlyDefinedCachePath' ) diff --git a/tools/findMissingNotices.ps1 b/tools/findMissingNotices.ps1 index 1346148baee..884eff50664 100644 --- a/tools/findMissingNotices.ps1 +++ b/tools/findMissingNotices.ps1 @@ -7,12 +7,14 @@ param( [switch] $Fix, - [switch] $IsStable + [switch] $IsStable, + [switch] $ForceHarvestedOnly ) Import-Module dotnet.project.assets Import-Module "$PSScriptRoot\..\.github\workflows\GHWorkflowHelper" -Force . "$PSScriptRoot\..\tools\buildCommon\startNativeExecution.ps1" +. "$PSScriptRoot\clearlyDefined\Find-LastHarvestedVersion.ps1" $packageSourceName = 'findMissingNoticesNugetOrg' if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) { @@ -20,7 +22,7 @@ if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) } $existingRegistrationTable = @{} -$cgManifestPath = (Resolve-Path -Path $PSScriptRoot\..\tools\cgmanifest.json).ProviderPath +$cgManifestPath = (Resolve-Path -Path $PSScriptRoot\cgmanifest\main\cgmanifest.json).ProviderPath $existingRegistrationsJson = Get-Content $cgManifestPath | ConvertFrom-Json -AsHashtable $existingRegistrationsJson.Registrations | ForEach-Object { $registration = [Registration]$_ @@ -335,8 +337,91 @@ if ($IsStable) { } $count = $newRegistrations.Count +$registrationsToSave = $newRegistrations +$tpnRegistrationsToSave = $null + +# If -ForceHarvestedOnly is specified with -Fix, only include harvested packages +# and revert non-harvested packages to their previous versions +if ($Fix -and $ForceHarvestedOnly) { + Write-Verbose "Checking harvest status and filtering to harvested packages with reversion..." -Verbose + + # Import ClearlyDefined module to check harvest status + Import-Module -Name "$PSScriptRoot/clearlyDefined/src/ClearlyDefined" -Force + + # Import cache from previous runs to speed up lookups + Import-ClearlyDefinedCache + + # Get harvest data for all registrations + $fullCgList = $newRegistrations | + ForEach-Object { + [PSCustomObject]@{ + type = $_.Component.Type + Name = $_.Component.Nuget.Name + PackageVersion = $_.Component.Nuget.Version + } + } + + $fullList = $fullCgList | Get-ClearlyDefinedData + + # Build a lookup table of harvest status by package name + version + $harvestStatus = @{} + foreach ($item in $fullList) { + $key = "$($item.Name)|$($item.PackageVersion)" + $harvestStatus[$key] = $item.harvested + } + + # Build a lookup table of old versions from existing manifest + $oldVersions = @{} + foreach ($registration in $existingRegistrationsJson.Registrations) { + $name = $registration.Component.Nuget.Name + if (!$oldVersions.ContainsKey($name)) { + $oldVersions[$name] = $registration + } + } + + # Process each new registration: keep harvested, revert non-harvested + $tpnRegistrationsToSave = @() + $harvestedCount = 0 + $revertedCount = 0 + + foreach ($reg in $newRegistrations) { + $name = $reg.Component.Nuget.Name + $version = $reg.Component.Nuget.Version + $key = "$name|$version" + + if ($harvestStatus.ContainsKey($key) -and $harvestStatus[$key]) { + # Package is harvested, include it + $tpnRegistrationsToSave += $reg + $harvestedCount++ + } else { + # Package not harvested, find last harvested version + $lastHarvestedVersion = Find-LastHarvestedVersion -Name $name -CurrentVersion $version + + # Use last harvested version if found, otherwise use old version as fallback + if ($lastHarvestedVersion) { + if ($lastHarvestedVersion -ne $version) { + $revertedReg = New-NugetComponent -Name $name -Version $lastHarvestedVersion -DevelopmentDependency:$reg.DevelopmentDependency + $tpnRegistrationsToSave += $revertedReg + $revertedCount++ + Write-Verbose "Reverted $name from v$version to last harvested v$lastHarvestedVersion" -Verbose + } else { + $tpnRegistrationsToSave += $reg + } + } elseif ($oldVersions.ContainsKey($name)) { + $tpnRegistrationsToSave += $oldVersions[$name] + $revertedCount++ + Write-Verbose "Reverted $name to previous version (no harvested version found)" -Verbose + } else { + Write-Warning "$name v$version not harvested and no previous version found. Excluding from manifest." + } + } + } + + Write-Verbose "Completed filtering for TPN: $harvestedCount harvested + $revertedCount reverted = $($tpnRegistrationsToSave.Count) total" -Verbose +} + $newJson = @{ - Registrations = $newRegistrations + Registrations = $registrationsToSave '$schema' = "https://json.schemastore.org/component-detection-manifest.json" } | ConvertTo-Json -depth 99 @@ -345,6 +430,149 @@ if ($Fix -and $registrationChanged) { Set-GWVariable -Name CGMANIFEST_PATH -Value $cgManifestPath } +# If -ForceHarvestedOnly was used, write the TPN manifest with filtered registrations +if ($Fix -and $ForceHarvestedOnly -and $tpnRegistrationsToSave.Count -gt 0) { + $tpnManifestDir = Join-Path -Path $PSScriptRoot -ChildPath "cgmanifest\tpn" + New-Item -ItemType Directory -Path $tpnManifestDir -Force | Out-Null + $tpnManifestPath = Join-Path -Path $tpnManifestDir -ChildPath "cgmanifest.json" + + $tpnManifest = @{ + Registrations = @($tpnRegistrationsToSave) + '$schema' = "https://json.schemastore.org/component-detection-manifest.json" + } + + $tpnJson = $tpnManifest | ConvertTo-Json -depth 99 + $tpnJson | Set-Content $tpnManifestPath -Encoding utf8NoBOM + Write-Verbose "TPN manifest created/updated with $($tpnRegistrationsToSave.Count) registrations (filtered for harvested packages)" -Verbose +} + +# Skip legacy TPN update when -ForceHarvestedOnly already produced a filtered manifest +if ($Fix -and $registrationChanged -and -not $ForceHarvestedOnly) { + # Import ClearlyDefined module to check harvest status + Write-Verbose "Checking harvest status for newly added packages..." -Verbose + Import-Module -Name "$PSScriptRoot/clearlyDefined/src/ClearlyDefined" -Force + + # Get harvest data for all registrations + $fullCgList = $newRegistrations | + ForEach-Object { + [PSCustomObject]@{ + type = $_.Component.Type + Name = $_.Component.Nuget.Name + PackageVersion = $_.Component.Nuget.Version + } + } + + $fullList = $fullCgList | Get-ClearlyDefinedData + $needHarvest = $fullList | Where-Object { !$_.harvested } + + if ($needHarvest.Count -gt 0) { + Write-Verbose "Found $($needHarvest.Count) packages that need harvesting. Starting harvest..." -Verbose + $needHarvest | Select-Object -ExpandProperty coordinates | ConvertFrom-ClearlyDefinedCoordinates | Start-ClearlyDefinedHarvest + } else { + Write-Verbose "All packages are already harvested." -Verbose + } + + # After manifest update and harvest, update TPN manifest with individual package status + Write-Verbose "Updating TPN manifest with individual package harvest status..." -Verbose + $tpnManifestDir = Join-Path -Path $PSScriptRoot -ChildPath "cgmanifest\tpn" + $tpnManifestPath = Join-Path -Path $tpnManifestDir -ChildPath "cgmanifest.json" + + # Load current TPN manifest to get previous versions + $currentTpnManifest = @() + if (Test-Path $tpnManifestPath) { + $currentTpnJson = Get-Content $tpnManifestPath | ConvertFrom-Json -AsHashtable + $currentTpnManifest = $currentTpnJson.Registrations + } + + # Build a lookup table of old versions + $oldVersions = @{} + foreach ($registration in $currentTpnManifest) { + $name = $registration.Component.Nuget.Name + if (!$oldVersions.ContainsKey($name)) { + $oldVersions[$name] = $registration + } + } + + # Note: Do not recheck harvest status here. Harvesting is an async process that takes a significant amount of time. + # Use the harvest data from the initial check. Newly triggered harvests will be captured + # on the next run of this script after harvesting completes. + $finalHarvestData = $fullList + + # Update packages individually based on harvest status + $tpnRegistrations = @() + $harvestedCount = 0 + $restoredCount = 0 + + foreach ($item in $finalHarvestData) { + $matchingNewRegistration = $newRegistrations | Where-Object { + $_.Component.Nuget.Name -eq $item.Name -and + $_.Component.Nuget.Version -eq $item.PackageVersion + } + + if ($matchingNewRegistration) { + if ($item.harvested) { + # Use new harvested version + $tpnRegistrations += $matchingNewRegistration + $harvestedCount++ + } else { + # Package not harvested - find the last harvested version from ClearlyDefined API + Write-Verbose "Finding last harvested version for $($item.Name)..." -Verbose + + $lastHarvestedVersion = $null + try { + # Search through all versions of this package to find the last harvested one + # Create a list of versions we know about from all runtimes + $packageVersionsToCheck = $newRegistrations | Where-Object { + $_.Component.Nuget.Name -eq $item.Name + } | ForEach-Object { $_.Component.Nuget.Version } | Sort-Object -Unique -Descending + + foreach ($versionToCheck in $packageVersionsToCheck) { + $versionCheckList = [PSCustomObject]@{ + type = "nuget" + Name = $item.Name + PackageVersion = $versionToCheck + } + + $versionStatus = $versionCheckList | Get-ClearlyDefinedData + if ($versionStatus -and $versionStatus.harvested) { + $lastHarvestedVersion = $versionToCheck + break # Found the most recent harvested version + } + } + } catch { + Write-Verbose "Error checking harvested versions for $($item.Name): $_" -Verbose + } + + # Use last harvested version if found, otherwise use old version as fallback + if ($lastHarvestedVersion) { + $revertedReg = New-NugetComponent -Name $item.Name -Version $lastHarvestedVersion -DevelopmentDependency:$matchingNewRegistration.DevelopmentDependency + $tpnRegistrations += $revertedReg + $restoredCount++ + Write-Verbose "Reverted $($item.Name) from v$($item.PackageVersion) to last harvested v$lastHarvestedVersion" -Verbose + } elseif ($oldVersions.ContainsKey($item.Name)) { + $tpnRegistrations += $oldVersions[$item.Name] + $restoredCount++ + Write-Verbose "Reverted $($item.Name) to previous version in TPN (no harvested version found)" -Verbose + } else { + Write-Warning "$($item.Name) v$($item.PackageVersion) not harvested and no harvested version found. Excluding from TPN manifest." + } + } + } + } + + # Save updated TPN manifest + if ($tpnRegistrations.Count -gt 0) { + $tpnManifest = @{ + Registrations = @($tpnRegistrations) + '$schema' = "https://json.schemastore.org/component-detection-manifest.json" + } + + $tpnJson = $tpnManifest | ConvertTo-Json -depth 99 + $tpnJson | Set-Content $tpnManifestPath -Encoding utf8NoBOM + Write-Verbose "TPN manifest updated: $harvestedCount new harvested + $restoredCount reverted to last harvested versions" -Verbose + } +} + if (!$Fix -and $registrationChanged) { $temp = Get-GWTempPath diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 16ae6b21fcc..53d895bc49b 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -4920,7 +4920,7 @@ function New-GlobalToolNupkgSource } # Set VSTS environment variable for CGManifest file path. - $globalToolCGManifestPFilePath = Join-Path -Path "$env:REPOROOT" -ChildPath "tools\cgmanifest.json" + $globalToolCGManifestPFilePath = Join-Path -Path "$env:REPOROOT" -ChildPath "tools/cgmanifest/main/cgmanifest.json" $globalToolCGManifestFilePath = Resolve-Path -Path $globalToolCGManifestPFilePath -ErrorAction SilentlyContinue if (($null -eq $globalToolCGManifestFilePath) -or (! (Test-Path -Path $globalToolCGManifestFilePath))) { From 95e38946b187512721018da5b265df77728adb77 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 26 Feb 2026 17:50:37 -0500 Subject: [PATCH 209/255] Add PMC packages for debian13 and rhel10 (#26912) --- tools/packages.microsoft.com/mapping.json | 42 +++++++++++++++-------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tools/packages.microsoft.com/mapping.json b/tools/packages.microsoft.com/mapping.json index 682c96d9110..334f6dfdd55 100644 --- a/tools/packages.microsoft.com/mapping.json +++ b/tools/packages.microsoft.com/mapping.json @@ -21,6 +21,13 @@ ], "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.rh.x86_64.rpm" }, + { + "url": "microsoft-rhel10.0-prod", + "distribution": [ + "stable" + ], + "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.rh.x86_64.rpm" + }, { "url": "cbl-mariner-2.0-prod-Microsoft-aarch64", "distribution": [ @@ -99,6 +106,27 @@ ], "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" }, + { + "url": "microsoft-debian-bullseye-prod", + "distribution": [ + "bullseye" + ], + "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" + }, + { + "url": "microsoft-debian-bookworm-prod", + "distribution": [ + "bookworm" + ], + "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" + }, + { + "url": "microsoft-debian-trixie-prod", + "distribution": [ + "trixie" + ], + "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" + }, { "url": "microsoft-ubuntu-bionic-prod", "distribution": [ @@ -133,20 +161,6 @@ "xenial" ], "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" - }, - { - "url": "microsoft-debian-bullseye-prod", - "distribution": [ - "bullseye" - ], - "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" - }, - { - "url": "microsoft-debian-bookworm-prod", - "distribution": [ - "bookworm" - ], - "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" } ] } From 3544ce5dbbd8539342f6b4b45b8d8623941eb62c Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 2 Mar 2026 12:40:22 -0800 Subject: [PATCH 210/255] Update PowerShell Profile DSC resource manifests to allow null for content (#26929) --- dsc/pwsh.profile.dsc.resource.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/pwsh.profile.dsc.resource.json b/dsc/pwsh.profile.dsc.resource.json index cd18e94eec6..aa5f5c29eee 100644 --- a/dsc/pwsh.profile.dsc.resource.json +++ b/dsc/pwsh.profile.dsc.resource.json @@ -90,7 +90,7 @@ "content": { "title": "Content", "description": "Defines the content of the profile. If you don't specify this property, the resource doesn't manage the file contents. If you specify this property as an empty string, the resource removes all content from the file. If you specify this property as a non-empty string, the resource sets the file contents to the specified string. The resources retains newlines from this property without any modification.", - "type": "string" + "type": [ "string", "null" ] }, "_exist": { "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json" From eb1915e76b511e1d7d1094c3dc2511065ed0c850 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:42:27 -0500 Subject: [PATCH 211/255] Hardcode Official templates (#26928) --- .../PowerShell-Coordinated_Packages-Official.yml | 11 +++-------- .pipelines/PowerShell-Packages-Official.yml | 11 +++-------- .pipelines/PowerShell-Release-Official-Azure.yml | 9 ++------- .pipelines/PowerShell-Release-Official.yml | 11 +++-------- .pipelines/PowerShell-vPack-Official.yml | 9 ++------- 5 files changed, 13 insertions(+), 38 deletions(-) diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml index e4de1fe5c21..380c9c5516e 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -29,11 +29,8 @@ parameters: displayName: Debugging - Enable CodeQL and set cadence to 1 hour type: boolean default: false - - name: OfficialBuild - type: boolean - default: false -name: bins-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId) +name: bins-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) resources: repositories: @@ -91,8 +88,6 @@ variables: value: true ${{ else }}: value: false - - name: templateFile - value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} # Fix for BinSkim ICU package error in Linux containers - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT value: true @@ -100,10 +95,10 @@ variables: - name: ob_sdl_binskim_enabled value: false - name: ps_official_build - value: ${{ parameters.OfficialBuild }} + value: true extends: - template: ${{ variables.templateFile }} + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: customTags: 'ES365AIMigrationTooling' featureFlags: diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml index 18ef7b2d14c..a13ef12378a 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -24,14 +24,11 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Signing type: string default: 'NO' - - name: OfficialBuild - type: boolean - default: false - name: disableNetworkIsolation type: boolean default: false -name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId) +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) variables: - name: CDP_DEFINITION_BUILD_COUNT @@ -67,8 +64,6 @@ variables: - name: branchCounter value: $[counter(variables['branchCounterKey'], 1)] - group: MSIXSigningProfile - - name: templateFile - value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} - name: disableNetworkIsolation value: ${{ parameters.disableNetworkIsolation }} @@ -89,7 +84,7 @@ resources: ref: refs/heads/main extends: - template: ${{ variables.templateFile }} + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: cloudvault: enabled: false @@ -294,7 +289,7 @@ extends: jobs: - template: /.pipelines/templates/package-create-msix.yml@self parameters: - OfficialBuild: ${{ parameters.OfficialBuild }} + OfficialBuild: true - stage: upload displayName: 'Upload' diff --git a/.pipelines/PowerShell-Release-Official-Azure.yml b/.pipelines/PowerShell-Release-Official-Azure.yml index f4c41143b5f..81543420460 100644 --- a/.pipelines/PowerShell-Release-Official-Azure.yml +++ b/.pipelines/PowerShell-Release-Official-Azure.yml @@ -13,11 +13,8 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Signing type: string default: 'NO' - - name: OfficialBuild - type: boolean - default: false -name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId) +name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) variables: - name: CDP_DEFINITION_BUILD_COUNT @@ -49,8 +46,6 @@ variables: - name: LinuxContainerImage value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - group: PoolNames - - name: templateFile - value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} resources: repositories: @@ -72,7 +67,7 @@ resources: - releases/* extends: - template: ${{ variables.templateFile }} + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: featureFlags: WindowsHostVersion: diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml index 868d61ebfd0..fa14b9b0acb 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -29,11 +29,8 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip MSIX Publish type: boolean default: false - - name: OfficialBuild - type: boolean - default: false -name: release-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId) +name: release-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) variables: - name: CDP_DEFINITION_BUILD_COUNT @@ -65,10 +62,8 @@ variables: - name: ReleaseTagVar value: ${{ parameters.ReleaseTagVar }} - group: PoolNames - - name: templateFile - value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} - name: releaseEnvironment - value: ${{ iif ( parameters.OfficialBuild, 'Production', 'Test' ) }} + value: 'Production' # Fix for BinSkim ICU package error in Linux containers - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT value: true @@ -97,7 +92,7 @@ resources: - releases/* extends: - template: ${{ variables.templateFile }} + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: release: category: NonAzure diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index 096dfb574a4..fbbf3683db5 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -1,9 +1,6 @@ trigger: none parameters: # parameters are shown up in ADO UI in a build queue time -- name: OfficialBuild - type: boolean - default: true - name: 'createVPack' displayName: 'Create and Submit VPack' type: boolean @@ -33,7 +30,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time - Netlock default: "R1" -name: vPack_$(Build.SourceBranchName)_Prod.${{ parameters.OfficialBuild }}_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) +name: vPack_$(Build.SourceBranchName)_Prod.true_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) variables: - name: CDP_DEFINITION_BUILD_COUNT @@ -58,8 +55,6 @@ variables: value: ${{ parameters.ReleaseTagVar }} - group: Azure Blob variable group - group: certificate_logical_to_actual # used within signing task - - name: templateFile - value: ${{ iif ( parameters.OfficialBuild, 'v2/Microsoft.Official.yml@onebranchTemplates', 'v2/Microsoft.NonOfficial.yml@onebranchTemplates' ) }} - group: DotNetPrivateBuildAccess - group: certificate_logical_to_actual - name: netiso @@ -75,7 +70,7 @@ resources: ref: refs/heads/main extends: - template: ${{ variables.templateFile }} + template: v2/Microsoft.Official.yml@onebranchTemplates parameters: platform: name: 'windows_undocked' # windows undocked From 2c14e1147014a2be863e7b48d1d155b2778997d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:41:33 -0700 Subject: [PATCH 212/255] Bump github/codeql-action from 4.32.4 to 4.32.6 (#26942) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index fbbb5c31efe..21966cb855c 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index c51e85b9448..ee70756298b 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5 with: sarif_file: results.sarif From 1ee3d7116d20143cbc06cbb71c56390fb432c7d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:41:56 -0700 Subject: [PATCH 213/255] Bump actions/dependency-review-action from 4.8.3 to 4.9.0 (#26938) --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 6bace648853..79f45e9eb4d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 From e170b7210a375b2ed75f3d2e5e41772858b97679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:13:30 -0700 Subject: [PATCH 214/255] Bump actions/upload-artifact from 6 to 7 (#26914) --- .github/workflows/macos-ci.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/windows-packaging-reusable.yml | 2 +- .github/workflows/xunit-tests.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index c7717a26738..5f363e0c265 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -229,7 +229,7 @@ jobs: testResultsFolder: "${{ runner.workspace }}/testResults" - name: Upload package artifact if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: macos-package path: "*.pkg" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index ee70756298b..7e868e10dbf 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index bb4873adeb3..55715c42a4c 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -81,7 +81,7 @@ jobs: - name: Upload Build Artifacts if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} path: | diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index 5d225446cb7..a1c86bea70a 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -46,7 +46,7 @@ jobs: Write-Host "Completed xUnit test run." - name: Upload xUnit results - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: always() with: name: ${{ inputs.test_results_artifact_name }} From 312291762e0bfc2467481aac4f22b48df02107ed Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:16:18 -0400 Subject: [PATCH 215/255] Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26893) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Co-authored-by: Dongbo Wang --- DotnetRuntimeMetadata.json | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 2 +- .../ResultsComparer/ResultsComparer.csproj | 2 +- test/tools/TestService/TestService.csproj | 2 +- test/xUnit/xUnit.tests.csproj | 2 +- tools/cgmanifest/main/cgmanifest.json | 8 +++--- .../src/ClearlyDefined/ClearlyDefined.psm1 | 27 +++++++++++++------ 8 files changed, 29 insertions(+), 18 deletions(-) diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index 1ae71bf0737..7c4a2191467 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -4,7 +4,7 @@ "quality": "daily", "qualityFallback": "preview", "packageVersionPattern": "9.0.0-preview.6", - "sdkImageVersion": "10.0.101", + "sdkImageVersion": "11.0.100-preview.1.26104.118", "nextChannel": "9.0.0-preview.7", "azureFeed": "", "sdkImageOverride": "" diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 56fa91bcf6b..b2fae8b21e7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index daa29bd01f3..5f680de58dc 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index 90143a9f482..58ea02cf1b5 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -10,6 +10,6 @@ - +
diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index 5d5f1a9e4f9..7580229ce98 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -16,7 +16,7 @@ - + diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 6ce2ee32ceb..4cf097c7956 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -30,7 +30,7 @@ all
- + diff --git a/tools/cgmanifest/main/cgmanifest.json b/tools/cgmanifest/main/cgmanifest.json index a0746028a56..4d856723eaa 100644 --- a/tools/cgmanifest/main/cgmanifest.json +++ b/tools/cgmanifest/main/cgmanifest.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/component-detection-manifest.json", "Registrations": [ { "Component": { @@ -65,7 +66,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.45.0" + "Version": "1.0.0" } }, "DevelopmentDependency": false @@ -495,7 +496,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.SqlClient", - "Version": "4.9.0" + "Version": "4.9.1" } }, "DevelopmentDependency": false @@ -750,6 +751,5 @@ }, "DevelopmentDependency": false } - ], - "$schema": "https://json.schemastore.org/component-detection-manifest.json" + ] } diff --git a/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 b/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 index 4e3d375dc5a..88fc1f7cabd 100644 --- a/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 +++ b/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 @@ -40,19 +40,30 @@ function ConvertFrom-ClearlyDefinedCoordinates { [CmdletBinding()] param( [parameter(mandatory = $true, ValueFromPipeline = $true)] - [string] + [object] $Coordinates ) Begin {} Process { - $parts = $Coordinates.Split('/') - [PSCustomObject]@{ - type = $parts[0] - provider = $parts[1] - namespace = $parts[2] - name = $parts[3] - revision = $parts[4] + if ($Coordinates -is [string]) { + $parts = $Coordinates.Split('/') + [PSCustomObject]@{ + type = $parts[0] + provider = $parts[1] + namespace = $parts[2] + name = $parts[3] + revision = $parts[4] + } + } else { + # Coordinates is already an object (e.g., from ClearlyDefined API response) + [PSCustomObject]@{ + type = $Coordinates.type + provider = $Coordinates.provider + namespace = $Coordinates.namespace + name = $Coordinates.name + revision = $Coordinates.revision + } } } End {} From a76568c22d0a6015d304c8773e2fd53a56907c53 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:55:00 -0500 Subject: [PATCH 216/255] Add 7.4.14 changelog (#26998) Co-authored-by: Justin Chung Co-authored-by: Travis Plunk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG/7.4.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/CHANGELOG/7.4.md b/CHANGELOG/7.4.md index 89a3f64e37c..f999c20315f 100644 --- a/CHANGELOG/7.4.md +++ b/CHANGELOG/7.4.md @@ -1,5 +1,82 @@ # 7.4 Changelog +## [7.4.14] + +### General Cmdlet Updates and Fixes + +- Fix `PSMethodInvocationConstraints.GetHashCode` method (#26959) + +### Tools + +- Add merge conflict marker detection to `linux-ci` workflow and refactor existing actions to use reusable `get-changed-files` action (#26362) +- Add reusable `get-changed-files` action and refactor existing actions (#26361) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26342) + +### Tests + +- Skip the flaky `Update-Help` test for the `PackageManagement` module (#26871) +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26869) +- Add GitHub Actions annotations for Pester test failures (#26800) +- Mark flaky `Update-Help` web tests as pending to unblock CI (#26805) +- Update the `Update-Help` tests to use `-Force` to remove read-only files (#26786) +- Fix merge conflict checker for empty file lists and filter `*.cs` files (#26387) +- Add markdown link verification for PRs (#26340) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.419

+ +
+ +
    +
  • Update MaxVisitCount and MaxHashtableKeyCount if visitor safe value context indicates SkipLimitCheck is true (Internal 38882)
  • +
  • Hardcode Official templates (#26962)
  • +
  • Split TPN manifest and Component Governance manifest (#26961)
  • +
  • Correct the package name for .deb and .rpm packages (#26960)
  • +
  • Bring over all changes for MSIX packaging template (#26933)
  • +
  • .NET Resolution and Store Publishing Updates (#26930)
  • +
  • Update Application Insights package version to 2.23.0 (#26883)
  • +
  • Update metadata.json to update the Latest attribute with a better name (#26872)
  • +
  • Update Get-ChangeLog to handle backport PRs correctly (#26870)
  • +
  • Remove unused runCodesignValidationInjection variable from pipeline templates (#26868)
  • +
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26864)
  • +
  • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26863)
  • +
  • Fix macOS preview package identifier detection to use version string (#26774)
  • +
  • Update the macOS package name for preview releases to match the previous pattern (#26435)
  • +
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26434)
  • +
  • Fix template path for rebuild branch check in package.yml (#26433)
  • +
  • Add rebuild branch support with conditional MSIX signing (#26418)
  • +
  • Move package validation to package pipeline (#26417)
  • +
  • Backport Store publishing improvements (#26401)
  • +
  • Fix path to metadata.json in channel selection script (#26399)
  • +
  • Optimize/split Windows package signing (#26413)
  • +
  • Improve ADO package build and validation across platforms (#26405)
  • +
  • Separate Store Automation Service Endpoints, Resolve AppID (#26396)
  • +
  • Fix the task name to not use the pre-release task (#26395)
  • +
  • Remove usage of fpm for DEB package generation (#26382)
  • +
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26344)
  • +
  • Replace fpm with native rpmbuild for RPM package generation (#26337)
  • +
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26363)
  • +
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26336)
  • +
  • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26335)
  • +
  • Add network isolation policy parameter to vPack pipeline (#26339)
  • +
  • GitHub Workflow cleanup (#26334)
  • +
  • Add build to vPack Pipeline (#25980)
  • +
  • Update vPack name (#26222)
  • +
+ +
+ +### Documentation and Help Content + +- Update Third Party Notices (#26892) + +[7.4.14]: https://github.com/PowerShell/PowerShell/compare/v7.4.13...v7.4.14 + ## [7.4.13] ### Build and Packaging Improvements From 7498139bae4a8ef40a6df9c845a4e9867dbcb22d Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 11 Mar 2026 16:39:10 -0400 Subject: [PATCH 217/255] Update `Microsoft.PowerShell.PSResourceGet` version to 1.2.0 (#27003) --- src/Modules/PSGalleryModules.csproj | 2 +- tools/packaging/boms/windows.json | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index 9136df5c7b3..3903b164b09 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -13,7 +13,7 @@ - + diff --git a/tools/packaging/boms/windows.json b/tools/packaging/boms/windows.json index 8248b3d03aa..8d900902bc2 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -1216,6 +1216,11 @@ "FileType": "NonProduct", "Architecture": null }, + { + "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\.signature.p7s", + "FileType": "NonProduct", + "Architecture": null + }, { "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\Microsoft.PowerShell.PSResourceGet.pdb", "FileType": "NonProduct", From fffe6094cf6d19593e77c803259c6d8a6ce0f18a Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 12 Mar 2026 11:37:25 -0700 Subject: [PATCH 218/255] Update changelog for release v7.5.5 (#27014) --- CHANGELOG/7.5.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/CHANGELOG/7.5.md b/CHANGELOG/7.5.md index 80b3ee0a8aa..d908532ff82 100644 --- a/CHANGELOG/7.5.md +++ b/CHANGELOG/7.5.md @@ -1,5 +1,90 @@ # 7.5 Changelog +## [7.5.5] + +### Engine Updates and Fixes + +- Fix up `SSHConnectionInfo` ssh PATH checks (#26165) (Thanks @jborean93!) + +### General Cmdlet Updates and Fixes + +- Close pipe client handles after creating the child ssh process (#26822) +- Fix the progress preference variable in script cmdlets (#26791) (Thanks @cmkb3!) + +### Tools + +- Add merge conflict marker detection to `linux-ci` workflow and refactor existing actions to use reusable `get-changed-files` action (#26812) +- Add reusable `get-changed-files` action and refactor existing actions (#26811) +- Create GitHub Copilot setup workflow (#26807) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26799) + +### Tests + +- Mark flaky `Update-Help` web tests as pending to unblock CI (#26837) +- Add GitHub Actions annotations for Pester test failures (#26836) +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26823) +- Fix merge conflict checker for empty file lists and filter `*.cs` files (#26813) +- Update the `Update-Help` tests to use `-Force` to remove read-only files (#26788) +- Add markdown link verification for PRs (#26407) + +### Build and Packaging Improvements + +
+ + +

Update to .NET SDK 9.0.312

+

We thank the following contributors!

+

@kasperk81, @RichardSlater

+ +
+ +
    +
  • Revert change to module name ThreadJob (#26997)
  • +
  • Update branch for release (#26990)
  • +
  • Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26987)
  • +
  • Update CGManifests (#26981)
  • +
  • Hardcode Official templates (#26968)
  • +
  • Split TPN manifest and Component Governance manifest (#26967)
  • +
  • Fix a preview detection test for the packaging script (#26966)
  • +
  • Correct the package name for .deb and .rpm packages (#26964)
  • +
  • Bring Release Changes from v7.6.0-preview.6 (#26963)
  • +
  • Merge the v7.6.0-preview.5 release branch back to master (#26958)
  • +
  • Fix macOS preview package identifier detection to use version string (#26835)
  • +
  • Update metadata.json to update the Latest attribute with a better name (#26826)
  • +
  • Remove unused runCodesignValidationInjection variable from pipeline templates (#26825)
  • +
  • Update Get-ChangeLog to handle backport PRs correctly (#26824)
  • +
  • Mirror .NET/runtime ICU version range in PowerShell (#26821) (Thanks @kasperk81!)
  • +
  • Update the macos package name for preview releases to match the previous pattern (#26820)
  • +
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26819)
  • +
  • Fix template path for rebuild branch check in package.yml (#26818)
  • +
  • Add rebuild branch support with conditional MSIX signing (#26817)
  • +
  • Move package validation to package pipeline (#26816)
  • +
  • Optimize/split windows package signing (#26815)
  • +
  • Improve ADO package build and validation across platforms (#26814)
  • +
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26810)
  • +
  • Remove usage of fpm for DEB package generation (#26809)
  • +
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26801)
  • +
  • Fix build to only enable ready-to-run for the Release configuration (#26798)
  • +
  • Fix R2R for fxdependent packaging (#26797)
  • +
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26794)
  • +
  • Replace fpm with native rpmbuild for RPM package generation (#26793)
  • +
  • Add libicu76 dependency to support Debian 13 (#26792) (Thanks @RichardSlater!)
  • +
  • Specify .NET search by build type (#26408)
  • +
  • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26773)
  • +
  • Fix path to metadata.json in channel selection script (#26400)
  • +
  • Separate store automation service endpoints and resolve AppID (#26266)
  • +
  • Update a few packages to use the right version corresponding to .NET 9 (#26671)
  • +
  • Add network isolation policy parameter to vPack pipeline (#26393)
  • +
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26391)
  • +
  • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26390)
  • +
  • GitHub Workflow cleanup (#26389)
  • +
  • Update vPack name (#26221)
  • +
+ +
+ +[7.5.5]: https://github.com/PowerShell/PowerShell/compare/v7.5.4...v7.5.5 + ## [7.5.4] ### Build and Packaging Improvements From 1f8bbe1e5332bb8fe1b39c81c366ddadbf9dd1a2 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 12 Mar 2026 12:34:12 -0700 Subject: [PATCH 219/255] Fix the container image for vPack, MSIX vPack and Package pipelines (#27015) --- .pipelines/MSIXBundle-vPack-Official.yml | 5 ++++- .pipelines/PowerShell-Coordinated_Packages-Official.yml | 3 ++- .pipelines/PowerShell-vPack-Official.yml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pipelines/MSIXBundle-vPack-Official.yml b/.pipelines/MSIXBundle-vPack-Official.yml index 997b7c458be..876da9c2aff 100644 --- a/.pipelines/MSIXBundle-vPack-Official.yml +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -22,7 +22,7 @@ variables: BuildSolution: $(Build.SourcesDirectory)\dirs.proj ReleaseTagVar: ${{ parameters.ReleaseTagVar }} BuildConfiguration: Release - WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' Codeql.Enabled: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 @@ -46,6 +46,9 @@ resources: extends: template: v2/Microsoft.Official.yml@onebranchTemplates parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 platform: name: 'windows_undocked' # windows undocked diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml index 380c9c5516e..12460e2861c 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -68,7 +68,7 @@ variables: - name: LinuxContainerImage value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - name: WindowsContainerImage - value: onebranch.azurecr.io/windows/ltsc2019/vse2022:latest + value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest - name: CDP_DEFINITION_BUILD_COUNT value: $[counter('', 0)] - name: ReleaseTagVar @@ -105,6 +105,7 @@ extends: LinuxHostVersion: Network: KS3 WindowsHostVersion: + Version: 2022 Network: KS3 incrementalSDLBinaryAnalysis: true globalSdl: diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index fbbf3683db5..c49560bf2b8 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -42,7 +42,7 @@ variables: - name: BuildConfiguration value: Release - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - name: Codeql.Enabled value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack - name: DOTNET_CLI_TELEMETRY_OPTOUT From 8c9c9e11872c8d98c2e7d7a1ed991fc7e1d07354 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 16 Mar 2026 12:40:26 -0700 Subject: [PATCH 220/255] Create LTS pkg and non-LTS pkg for macOS for LTS releases (#27039) --- .pipelines/templates/mac-package-build.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index def866173ac..6585773c743 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -102,6 +102,10 @@ jobs: Restore-PSOptions -PSOptionsPath "$psoptionsPath" Get-PSOptions | Write-Verbose -Verbose + if (-not (Test-Path "$repoRoot/tools/metadata.json")) { + throw "metadata.json not found in $repoRoot/tools" + } + $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json Write-Verbose -Verbose "metadata:" @@ -120,14 +124,19 @@ jobs: Write-Verbose -Verbose "LTS: $LTS" if ($LTS) { - Write-Verbose -Message "LTS Release: $LTS" + Write-Verbose -Message "LTS Release: $LTS" -Verbose } Start-PSBootstrap -Scenario Package $macosRuntime = "osx-$buildArch" - Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + + if ($LTS) { + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + } + $pkgNameFilter = "powershell-*$macosRuntime.pkg" Write-Verbose -Verbose "Looking for pkg packages with filter: $pkgNameFilter in '$(Pipeline.Workspace)' to upload..." $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File From e053a339a1109ed16d36d77b9a47ab35ad4d017d Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 17 Mar 2026 14:47:59 -0700 Subject: [PATCH 221/255] Create Linux LTS deb/rpm packages for LTS releases (#27049) --- .pipelines/templates/linux-package-build.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml index ea49b9fea5d..bb7ed723f42 100644 --- a/.pipelines/templates/linux-package-build.yml +++ b/.pipelines/templates/linux-package-build.yml @@ -155,10 +155,6 @@ jobs: Write-Verbose -Verbose "LTS: $LTS" - if ($LTS) { - Write-Verbose -Message "LTS Release: $LTS" - } - if (-not (Test-Path $(ob_outputDirectory))) { New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } @@ -168,6 +164,11 @@ jobs: Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" -Verbose + Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + } + $vstsCommandString = "vso[task.setvariable variable=PackageFilter]$pkgFilter" Write-Host ("sending " + $vstsCommandString) Write-Host "##$vstsCommandString" From 28a2e35f061ab45466b7c607127c85d2c1c4381c Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:46:37 +0000 Subject: [PATCH 222/255] Fix `IDisposable` implementation in sealed classes (#26215) Co-authored-by: Justin Chung <124807742+jshigetomi@users.noreply.github.com> --- .../commands/management/Hotfix.cs | 19 +--- .../commands/management/Process.cs | 11 +- .../commands/utility/Tee-Object.cs | 16 +-- .../commands/utility/XmlCommands.cs | 3 +- .../engine/Modules/GetModuleCommand.cs | 18 +--- .../engine/Modules/ImportModuleCommand.cs | 16 +-- .../engine/MshCommandRuntime.cs | 11 +- .../engine/hostifaces/PowerShell.cs | 100 +++++++----------- .../hostifaces/PowerShellProcessInstance.cs | 37 +++---- .../engine/hostifaces/RunspacePool.cs | 2 +- .../utils/CryptoUtils.cs | 15 +-- 11 files changed, 81 insertions(+), 167 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs index 1aa94509469..d0f15346396 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs @@ -206,26 +206,11 @@ private bool FilterMatch(ManagementObject obj) #region "IDisposable Members" /// - /// Dispose Method. + /// Release all resources. /// public void Dispose() { - this.Dispose(true); - // Use SuppressFinalize in case a subclass - // of this type implements a finalizer. - GC.SuppressFinalize(this); - } - - /// - /// Dispose Method. - /// - /// - public void Dispose(bool disposing) - { - if (disposing) - { - _searchProcess?.Dispose(); - } + _searchProcess?.Dispose(); } #endregion "IDisposable Members" diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index c959cfcb33e..91efda12263 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -2188,15 +2188,12 @@ protected override void BeginProcessing() #region IDisposable Overrides /// - /// Dispose WaitHandle used to honor -Wait parameter. + /// Release all resources. /// + /// + /// Dispose WaitHandle used to honor -Wait parameter. + /// public void Dispose() - { - Dispose(true); - System.GC.SuppressFinalize(this); - } - - private void Dispose(bool isDisposing) { _cancellationTokenSource.Dispose(); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs index 27ea15406f7..4a0f4831299 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs @@ -140,12 +140,15 @@ protected override void EndProcessing() _commandWrapper.ShutDown(); } - private void Dispose(bool isDisposing) + /// + /// Release all resources. + /// + public void Dispose() { if (!_alreadyDisposed) { _alreadyDisposed = true; - if (isDisposing && _commandWrapper != null) + if (_commandWrapper != null) { _commandWrapper.Dispose(); _commandWrapper = null; @@ -153,15 +156,6 @@ private void Dispose(bool isDisposing) } } - /// - /// Dispose method in IDisposable. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - #region private private CommandWrapper _commandWrapper; private bool _alreadyDisposed; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs index 6473d6d4fae..cc3b1f2b251 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs @@ -331,13 +331,12 @@ public string[] LiteralPath private bool _disposed = false; /// - /// Public dispose method. + /// Release all resources. /// public void Dispose() { if (!_disposed) { - GC.SuppressFinalize(this); if (_helper != null) { _helper.Dispose(); diff --git a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs index 39c3e9c905e..caf7527d73e 100644 --- a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs @@ -297,29 +297,17 @@ protected override void StopProcessing() #region IDisposable Members /// - /// Releases resources associated with this object. + /// Release all resources. /// public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases resources associated with this object. - /// - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _cancellationTokenSource.Dispose(); - } - + _cancellationTokenSource.Dispose(); + _disposed = true; } diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 05357bbb5e5..532079d1a77 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -1780,28 +1780,16 @@ protected override void StopProcessing() #region IDisposable Members /// - /// Releases resources associated with this object. + /// Release all resources. /// public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases resources associated with this object. - /// - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _cancellationTokenSource.Dispose(); - } + _cancellationTokenSource.Dispose(); _disposed = true; } diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index d5f821bc930..e674cb1c5eb 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -2372,19 +2372,18 @@ internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPip _pp._permittedToWriteToPipeline = permittedToWriteToPipeline; _pp._permittedToWriteThread = Thread.CurrentThread; } + /// - /// End the scope where WriteObject/WriteError is permitted. + /// Release all resources. /// - /// + /// + /// End the scope where WriteObject/WriteError is permitted. + /// public void Dispose() { _pp._permittedToWrite = _wasPermittedToWrite; _pp._permittedToWriteToPipeline = _wasPermittedToWriteToPipeline; _pp._permittedToWriteThread = _wasPermittedToWriteThread; - GC.SuppressFinalize(this); } // There is no finalizer, by design. This class relies on always diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs index c86b86b4ffb..3af978ea165 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs @@ -3880,15 +3880,50 @@ private void PipelineStateChanged(object source, PipelineStateEventArgs stateEve #region IDisposable Overrides /// - /// Dispose all managed resources. This will suppress finalizer on the object from getting called by - /// calling System.GC.SuppressFinalize(this). + /// Release all resources. /// public void Dispose() { - Dispose(true); - // To prevent derived types with finalizers from having to re-implement System.IDisposable to call it, - // unsealed types without finalizers should still call SuppressFinalize. - System.GC.SuppressFinalize(this); + lock (_syncObject) + { + // if already disposed return + if (_isDisposed) + { + return; + } + } + + // Stop the currently running command outside of the lock + if (InvocationStateInfo.State == PSInvocationState.Running || + InvocationStateInfo.State == PSInvocationState.Stopping) + { + Stop(); + } + + lock (_syncObject) + { + _isDisposed = true; + } + + if (OutputBuffer != null && OutputBufferOwner) + { + OutputBuffer.Dispose(); + } + + if (_errorBuffer != null && ErrorBufferOwner) + { + _errorBuffer.Dispose(); + } + + if (IsRunspaceOwner) + { + _runspace.Dispose(); + } + + RemotePowerShell?.Dispose(); + + _invokeAsyncResult = null; + _stopAsyncResult = null; } #endregion @@ -4121,59 +4156,6 @@ private void AssertNotDisposed() } } - /// - /// Release all the resources. - /// - /// - /// if true, release all the managed objects. - /// - private void Dispose(bool disposing) - { - if (disposing) - { - lock (_syncObject) - { - // if already disposed return - if (_isDisposed) - { - return; - } - } - - // Stop the currently running command outside of the lock - if (InvocationStateInfo.State == PSInvocationState.Running || - InvocationStateInfo.State == PSInvocationState.Stopping) - { - Stop(); - } - - lock (_syncObject) - { - _isDisposed = true; - } - - if (OutputBuffer != null && OutputBufferOwner) - { - OutputBuffer.Dispose(); - } - - if (_errorBuffer != null && ErrorBufferOwner) - { - _errorBuffer.Dispose(); - } - - if (IsRunspaceOwner) - { - _runspace.Dispose(); - } - - RemotePowerShell?.Dispose(); - - _invokeAsyncResult = null; - _stopAsyncResult = null; - } - } - /// /// Clear the internal elements. /// diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs index 5ab95041877..f86d2c00a54 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs @@ -179,15 +179,9 @@ public bool HasExited #region Dispose /// - /// Implementing the interface. + /// Release all resources. /// public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) { if (_isDisposed) { @@ -203,23 +197,20 @@ private void Dispose(bool disposing) _isDisposed = true; } - - if (disposing) + + try + { + if (Process != null && !Process.HasExited) + Process.Kill(); + } + catch (InvalidOperationException) + { + } + catch (Win32Exception) + { + } + catch (NotSupportedException) { - try - { - if (Process != null && !Process.HasExited) - Process.Kill(); - } - catch (InvalidOperationException) - { - } - catch (Win32Exception) - { - } - catch (NotSupportedException) - { - } } } diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs index 7dc9e8d31de..e7cfb888a88 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs @@ -1199,7 +1199,7 @@ public void EndClose(IAsyncResult asyncResult) } /// - /// Dispose the current runspacepool. + /// Release all resources. /// public void Dispose() { diff --git a/src/System.Management.Automation/utils/CryptoUtils.cs b/src/System.Management.Automation/utils/CryptoUtils.cs index dfda3032728..c82c96ecdc3 100644 --- a/src/System.Management.Automation/utils/CryptoUtils.cs +++ b/src/System.Management.Automation/utils/CryptoUtils.cs @@ -576,21 +576,12 @@ internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForServer( #region IDisposable /// - /// Dispose resources. + /// Release all resources. /// public void Dispose() { - Dispose(true); - System.GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - _rsa?.Dispose(); - _aes?.Dispose(); - } + _rsa?.Dispose(); + _aes?.Dispose(); } #endregion IDisposable From 73186fb1382b7c5207a8c59d1269dc35adc7b584 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 18 Mar 2026 16:42:23 -0400 Subject: [PATCH 223/255] Fix PMC repo URL for RHEL10 (#27059) --- .pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 | 2 +- tools/packages.microsoft.com/mapping.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 index 23f91c1bff2..6797ff94575 100644 --- a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 +++ b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 @@ -64,7 +64,7 @@ function Get-MappedRepositoryIds { $repoIds.AddRange(([string[]]$repos.id)) } else { - Write-Failure "Could not find repo for $urlGlob" + throw "Could not find repo for $urlGlob" } if ($repoIds.Count -gt 0) { diff --git a/tools/packages.microsoft.com/mapping.json b/tools/packages.microsoft.com/mapping.json index 334f6dfdd55..154a9582872 100644 --- a/tools/packages.microsoft.com/mapping.json +++ b/tools/packages.microsoft.com/mapping.json @@ -22,7 +22,7 @@ "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.rh.x86_64.rpm" }, { - "url": "microsoft-rhel10.0-prod", + "url": "microsoft-rhel10-prod", "distribution": [ "stable" ], From 569eb7246c92d35953c8448352e7ae87c0dc3f56 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:28:37 -0400 Subject: [PATCH 224/255] Move `_GetDependencies` MSBuild target from dynamic generation in `build.psm1` into `Microsoft.PowerShell.SDK.csproj` (#27052) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- build.psm1 | 25 ++----------------- .../Microsoft.PowerShell.SDK.csproj | 24 +++++++++++++++++- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/build.psm1 b/build.psm1 index ee50c9cde51..ec91f2cf314 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2681,38 +2681,17 @@ function Start-TypeGen # Add .NET CLI tools to PATH Find-Dotnet - # This custom target depends on 'ResolveAssemblyReferencesDesignTime', whose definition can be found in the sdk folder. - # To find the available properties of '_ReferencesFromRAR' when switching to a new dotnet sdk, follow the steps below: - # 1. create a dummy project using the new dotnet sdk. - # 2. build the dummy project with this command: - # dotnet msbuild .\dummy.csproj /t:ResolveAssemblyReferencesDesignTime /fileLogger /noconsolelogger /v:diag - # 3. search '_ReferencesFromRAR' in the produced 'msbuild.log' file. You will find the properties there. - $GetDependenciesTargetPath = "$PSScriptRoot/src/Microsoft.PowerShell.SDK/obj/Microsoft.PowerShell.SDK.csproj.TypeCatalog.targets" - $GetDependenciesTargetValue = @' - - - - <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' "/> - - - - -'@ - New-Item -ItemType Directory -Path (Split-Path -Path $GetDependenciesTargetPath -Parent) -Force > $null - Set-Content -Path $GetDependenciesTargetPath -Value $GetDependenciesTargetValue -Force -Encoding Ascii - Push-Location "$PSScriptRoot/src/Microsoft.PowerShell.SDK" try { $ps_inc_file = "$PSScriptRoot/src/TypeCatalogGen/$IncFileName" - dotnet msbuild .\Microsoft.PowerShell.SDK.csproj /t:_GetDependencies "/property:DesignTimeBuild=true;_DependencyFile=$ps_inc_file" /nologo + Start-NativeExecution { dotnet msbuild .\Microsoft.PowerShell.SDK.csproj /t:_GetDependencies "/property:DesignTimeBuild=true;_DependencyFile=$ps_inc_file" /nologo } } finally { Pop-Location } Push-Location "$PSScriptRoot/src/TypeCatalogGen" try { - dotnet run ../System.Management.Automation/CoreCLR/CorePsTypeCatalog.cs $IncFileName + Start-NativeExecution { dotnet run ../System.Management.Automation/CoreCLR/CorePsTypeCatalog.cs $IncFileName } } finally { Pop-Location } diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 5f680de58dc..979764ff79f 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -31,4 +31,26 @@
- + + + + + <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' "/> + + + + + \ No newline at end of file From a15947e77af7f0a1c771abf10a26949d79669505 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 18 Mar 2026 15:12:50 -0700 Subject: [PATCH 225/255] Update `metadata.json` for the v7.6.0 release (#27054) --- tools/metadata.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/metadata.json b/tools/metadata.json index 1bf3e96e39c..57433aec4c6 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,9 +1,9 @@ { - "StableReleaseTag": "v7.5.4", + "StableReleaseTag": "v7.6.0", "PreviewReleaseTag": "v7.6.0-rc.1", "ServicingReleaseTag": "v7.0.13", - "ReleaseTag": "v7.5.4", - "LTSReleaseTag" : ["v7.4.13"], + "ReleaseTag": "v7.6.0", + "LTSReleaseTag": ["v7.4.14", "v7.6.0"], "NextReleaseTag": "v7.7.0-preview.1", "LTSRelease": { "PublishToChannels": false, "Package": false }, "StableRelease": { "PublishToChannels": false, "Package": false } From 78e437c6f30cceb2e6f9e3e66548afcce234005b Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 18 Mar 2026 15:32:15 -0700 Subject: [PATCH 226/255] Fix the `PSNativeCommandArgumentPassing` test (#27057) --- .../NativeExecution/NativeCommandArguments.Tests.ps1 | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 index 8e09df9b699..ead0fb39efb 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 @@ -5,12 +5,7 @@ param() Describe "Behavior is specific for each platform" -tags "CI" { It "PSNativeCommandArgumentPassing is set to 'Windows' on Windows systems" -skip:(-not $IsWindows) { - if ([Version]::TryParse($PSVersiontable.PSVersion.ToString(), [ref]$null)) { - $PSNativeCommandArgumentPassing | Should -BeExactly "Legacy" - } - else { - $PSNativeCommandArgumentPassing | Should -BeExactly "Windows" - } + $PSNativeCommandArgumentPassing | Should -BeExactly "Windows" } It "PSNativeCommandArgumentPassing is set to 'Standard' on non-Windows systems" -skip:($IsWindows) { $PSNativeCommandArgumentPassing | Should -Be "Standard" From 9c6a012e7dbf7956ab37e2bf1a239251d376a251 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:57:10 -0500 Subject: [PATCH 227/255] Update build to create two msix's and msixbundles for LTS and Stable (#27056) --- .pipelines/templates/package-create-msix.yml | 78 +++++++++++++++---- .../templates/packaging/windows/package.yml | 8 ++ tools/packaging/packaging.psm1 | 19 ++--- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index 255915ab02c..e461bb6efd9 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -85,17 +85,44 @@ jobs: $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose } - $file = Get-ChildItem $sourceDir | Select-Object -First 1 - $prefix = ($file.BaseName -split "-win")[0] - $pkgName = "$prefix.msixbundle" - Write-Verbose -Verbose "Creating $pkgName" - $makeappx = '$(MakeAppxPath)' $outputDir = "$sourceDir\output" New-Item $outputDir -Type Directory -Force > $null - & $makeappx bundle /d $sourceDir /p "$outputDir\$pkgName" - Get-ChildItem -Path $sourceDir -Recurse + # Separate LTS and Stable/Preview MSIX files by filename convention + $ltsMsix = @(Get-ChildItem $sourceDir -Filter '*.msix' | Where-Object { $_.BaseName -match '-LTS-' }) + $stableMsix = @(Get-ChildItem $sourceDir -Filter '*.msix' | Where-Object { $_.BaseName -notmatch '-LTS-' }) + + Write-Verbose -Verbose "Stable/Preview MSIX files: $($stableMsix.Name -join ', ')" + Write-Verbose -Verbose "LTS MSIX files: $($ltsMsix.Name -join ', ')" + + # Create Stable/Preview bundle + if ($stableMsix.Count -gt 0) { + $stableDir = "$sourceDir\stable" + New-Item $stableDir -Type Directory -Force > $null + $stableMsix | Copy-Item -Destination $stableDir -Force + $file = $stableMsix | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $stableBundleName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating Stable/Preview bundle: $stableBundleName" + & $makeappx bundle /d $stableDir /p "$outputDir\$stableBundleName" + } + + # Create LTS bundle + if ($ltsMsix.Count -gt 0) { + $ltsDir = "$sourceDir\lts" + New-Item $ltsDir -Type Directory -Force > $null + $ltsMsix | Copy-Item -Destination $ltsDir -Force + $file = $ltsMsix | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $ltsBundleName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating LTS bundle: $ltsBundleName" + & $makeappx bundle /d $ltsDir /p "$outputDir\$ltsBundleName" + } + + Write-Verbose -Verbose "Created bundles:" + Get-ChildItem -Path $outputDir -Recurse + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" Write-Host "sending " + $vstsCommandString Write-Host "##$vstsCommandString" @@ -112,16 +139,18 @@ jobs: search_root: '$(BundleDir)' - pwsh: | - $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File - Write-Verbose -Verbose "Signed bundle: $signedBundle" + $signedBundles = @(Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File) + Write-Verbose -Verbose "Signed bundles: $($signedBundles.Name -join ', ')" if (-not (Test-Path $(ob_outputDirectory))) { New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } - Copy-Item -Path $signedBundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + foreach ($bundle in $signedBundles) { + Copy-Item -Path $bundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + } - Write-Verbose -Verbose "Uploaded Bundle:" + Write-Verbose -Verbose "Uploaded Bundles:" Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose displayName: Upload msixbundle to Artifacts @@ -225,6 +254,29 @@ jobs: Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" + # Select the correct bundle based on channel + $bundleFiles = @(Get-ChildItem -Path '$(BundleDir)' -Filter '*.msixbundle') + Write-Verbose -Verbose "Available bundles: $($bundleFiles.Name -join ', ')" + + if ($IsLTS) { + $bundleFile = $bundleFiles | Where-Object { $_.Name -match '-LTS-' } + } else { + # Catches Stable or Preview + $bundleFile = $bundleFiles | Where-Object { $_.Name -notmatch '-LTS-' } + } + + if (-not $bundleFile) { + Write-Error "No matching bundle found for channel '$currentChannel'. Available bundles: $($bundleFiles.Name -join ', ')" + exit 1 + } + + # Copy the selected bundle to a dedicated directory for store packaging + $storeBundleDir = '$(Pipeline.Workspace)\releasePipeline\msix\store-bundle' + New-Item $storeBundleDir -Type Directory -Force > $null + Copy-Item -Path $bundleFile.FullName -Destination $storeBundleDir -Force -Verbose + Write-Host "##vso[task.setvariable variable=StoreBundleDir]$storeBundleDir" + Write-Verbose -Verbose "Selected bundle for store packaging: $($bundleFile.Name)" + # These variables are used in the next tasks to determine which ServiceEndpoint to use $ltsValue = $IsLTS.ToString().ToLower() $stableValue = $IsStable.ToString().ToLower() @@ -256,7 +308,7 @@ jobs: inputs: serviceEndpoint: 'StoreAppPublish-Preview' sbConfigPath: '$(SBConfigPath)' - sourceFolder: '$(BundleDir)' + sourceFolder: '$(StoreBundleDir)' contents: '*.msixBundle' outSBName: 'PowerShellStorePackage' pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' @@ -268,7 +320,7 @@ jobs: inputs: serviceEndpoint: 'StoreAppPublish-Stable' sbConfigPath: '$(SBConfigPath)' - sourceFolder: '$(BundleDir)' + sourceFolder: '$(StoreBundleDir)' contents: '*.msixBundle' outSBName: 'PowerShellStorePackage' pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml index 1f03d65ab21..92926e6aa59 100644 --- a/.pipelines/templates/packaging/windows/package.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -136,12 +136,14 @@ jobs: # Don't build LTS packages for rebuild branches $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + $Stable = [bool]$metadata.StableRelease.Package if ($isRebuildBranch) { Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose } Write-Verbose -Verbose "LTS: $LTS" + Write-Verbose -Verbose "Stable: $Stable" if ($LTS) { Write-Verbose -Message "LTS Release: $LTS" @@ -175,6 +177,12 @@ jobs: Start-PSPackage -Type $packageTypes -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS + # When both LTS and Stable are requested, also build the Stable MSIX + if ($packageTypes -contains 'msix' -and $LTS -and $Stable) { + Write-Verbose -Verbose "Both LTS and Stable packages requested. Building additional Stable MSIX." + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + } + displayName: 'Build Packages (Unsigned)' env: __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 53d895bc49b..baab911f60d 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -4274,18 +4274,8 @@ function New-MSIXPackage $makepri = Get-Item (Join-Path $makeappx.Directory "makepri.exe") -ErrorAction Stop + $displayName = $ProductName $ProductSemanticVersion = Get-PackageSemanticVersion -Version $ProductVersion - $productSemanticVersionWithName = $ProductName + '-' + $ProductSemanticVersion - $packageName = $productSemanticVersionWithName - if ($Private) { - $ProductNameSuffix = 'Private' - } - - if ($ProductNameSuffix) { - $packageName += "-$ProductNameSuffix" - } - - $displayName = $productName if ($Private) { $ProductName = 'PowerShell-Private' @@ -4301,6 +4291,13 @@ function New-MSIXPackage Write-Verbose -Verbose "ProductName: $productName" Write-Verbose -Verbose "DisplayName: $displayName" + $packageName = $ProductName + '-' + $ProductSemanticVersion + + # Appends Architecture to the package name + if ($ProductNameSuffix) { + $packageName += "-$ProductNameSuffix" + } + $ProductVersion = Get-WindowsVersion -PackageName $packageName # Any app that is submitted to the Store must have a PhoneIdentity in its appxmanifest. From a51e7be624ab6dbde17f1d35ef470b69b8ebc2db Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 19 Mar 2026 14:04:53 -0700 Subject: [PATCH 228/255] Check in `7.6.md` after v7.6.0 release (#27063) --- CHANGELOG/7.6.md | 807 +++++++++++++++++++++++++++++++++++++++++++ CHANGELOG/preview.md | 759 ---------------------------------------- 2 files changed, 807 insertions(+), 759 deletions(-) create mode 100644 CHANGELOG/7.6.md diff --git a/CHANGELOG/7.6.md b/CHANGELOG/7.6.md new file mode 100644 index 00000000000..47a8d33182f --- /dev/null +++ b/CHANGELOG/7.6.md @@ -0,0 +1,807 @@ +# 7.6 Changelog + +## [7.6.0] + +### General Cmdlet Updates and Fixes + +- Update PowerShell Profile DSC resource manifests to allow `null` for content (#26973) + +### Tests + +- Add GitHub Actions annotations for Pester test failures (#26969) +- Fix `Import-Module.Tests.ps1` to handle Arm32 platform (#26888) + +### Build and Packaging Improvements + +
+ + + +

Update to .NET SDK 10.0.201

+ +
+ +
    +
  • Update v7.6 release branch to use .NET SDK 10.0.201 (#27041)
  • +
  • Create LTS package and non-LTS package for macOS for LTS releases (#27040)
  • +
  • Fix the container image for package pipelines (#27020)
  • +
  • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0 (#27007)
  • +
  • Update LTS and Stable release settings in metadata (#27006)
  • +
  • Update branch for release (#26989)
  • +
  • Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26986)
  • +
  • Update NuGet package versions in cgmanifest.json to actually match the branch (#26982)
  • +
  • Bump actions/upload-artifact from 6 to 7 (#26979)
  • +
  • Split TPN manifest and Component Governance manifest (#26978)
  • +
  • Bump github/codeql-action from 4.32.4 to 4.32.6 (#26975)
  • +
  • Bump actions/dependency-review-action from 4.8.3 to 4.9.0 (#26974)
  • +
  • Hardcode Official templates (#26972)
  • +
  • Fix a preview detection test for the packaging script (#26971)
  • +
  • Add PMC packages for debian13 and rhel10 (#26917)
  • +
  • Add version in description and pass store task on failure (#26889)
  • +
  • Exclude .exe packages from publishing to GitHub (#26887)
  • +
  • Correct the package name for .deb and .rpm packages (#26884)
  • +
+ +
+ +[7.6.0]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-rc.1...v7.6.0 + +## [7.6.0-rc.1] - 2026-02-19 + +### Tests + +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26705) + +### Build and Packaging Improvements + +
+ + + +

Expand to see details.

+ +
+ +
    +
  • Update branch for release (#26779)
  • +
  • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0-rc3 (#26767)
  • +
  • Update Microsoft.PowerShell.Native package version (#26748)
  • +
  • Move PowerShell build to depend on .NET SDK 10.0.102 (#26717)
  • +
  • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26715)
  • +
  • Fix macOS preview package identifier detection to use version string (#26709)
  • +
  • Update metadata.json to update the Latest attribute with a better name (#26708)
  • +
  • Remove unused runCodesignValidationInjection variable from pipeline templates (#26707)
  • +
  • Update Get-ChangeLog to handle backport PRs correctly (#26706)
  • +
  • Bring release changes from the v7.6.0-preview.6 release (#26626)
  • +
  • Fix the DSC test by skipping AfterAll cleanup if the initial setup in BeforeAll failed (#26622)
  • +
+ +
+ +[7.6.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.6...v7.6.0-rc.1 + +## [7.6.0-preview.6] - 2025-12-11 + +### Engine Updates and Fixes + +- Properly Expand Aliases to their actual ResolvedCommand (#26571) (Thanks @kilasuit!) + +### General Cmdlet Updates and Fixes + +- Update `Microsoft.PowerShell.PSResourceGet` to `v1.2.0-preview5` (#26590) +- Make the experimental feature `PSFeedbackProvider` stable (#26502) +- Fix a regression in the API `CompletionCompleters.CompleteFilename()` that causes null reference exception (#26487) +- Add Delimiter parameter to `Get-Clipboard` (#26572) (Thanks @MartinGC94!) +- Close pipe client handles after creating the child ssh process (#26564) +- Make some experimental features stable (#26490) +- DSC v3 resource for PowerShell Profile (#26447) + +### Tools + +- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26530) +- Add reusable get-changed-files action and refactor existing actions (#26529) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26494) + +### Tests + +- Fix merge conflict checker for empty file lists and filter *.cs files (#26556) +- Add markdown link verification for PRs (#26445) + +### Build and Packaging Improvements + +
+ + + +

Expand to see details.

+ +
+ +
    +
  • Fix template path for rebuild branch check in package.yml (#26560)
  • +
  • Update the macos package name for preview releases to match the previous pattern (#26576)
  • +
  • Add rebuild branch support with conditional MSIX signing (#26573)
  • +
  • Update the WCF packages to the latest version that is compatible with v4.10.3 (#26503)
  • +
  • Improve ADO package build and validation across platforms (#26532)
  • +
  • Mirror .NET/runtime ICU version range in PowerShell (#26563) (Thanks @kasperk81!)
  • +
  • Update the macos package name for preview releases to match the previous pattern (#26562)
  • +
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26561)
  • +
  • Move package validation to package pipeline (#26558)
  • +
  • Optimize/split windows package signing (#26557)
  • +
  • Remove usage of fpm for DEB package generation (#26504)
  • +
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26524)
  • +
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26501)
  • +
  • Replace fpm with native rpmbuild for RPM package generation (#26441)
  • +
  • Fix GitHub API rate limit errors in test actions (#26492)
  • +
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26493)
  • +
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26488)
  • +
  • Fix build to only enable ready-to-run for the Release configuration (#26481)
  • +
  • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26468)
  • +
  • Update outdated package references (#26471)
  • +
  • GitHub Workflow cleanup (#26439)
  • +
  • Update PSResourceGet package version to preview4 (#26438)
  • +
  • Update PSReadLine to v2.4.5 (#26446)
  • +
  • Add network isolation policy parameter to vPack pipeline (#26444)
  • +
  • Fix a couple more lint errors
  • +
  • Fix lint errors in preview.md
  • +
  • Make MSIX publish stage dependent on SetReleaseTagandContainerName stage
  • +
+ +
+ +[7.6.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.5...v7.6.0-preview.6 + +## [7.6.0-preview.5] - 2025-09-30 + +### Engine Updates and Fixes + +- Allow opt-out of the named-pipe listener using the environment variable `POWERSHELL_DIAGNOSTICS_OPTOUT` (#26086) +- Ensure that socket timeouts are set only during the token validation (#26066) +- Fix race condition in `RemoteHyperVSocket` (#26057) +- Fix `stderr` output of console host to respect `NO_COLOR` (#24391) +- Update PSRP protocol to deprecate session key exchange between newer client and server (#25774) +- Fix the `ssh` PATH check in `SSHConnectionInfo` when the default Runspace is not available (#25780) (Thanks @jborean93!) +- Adding hex format for native command exit codes (#21067) (Thanks @sba923!) +- Fix infinite loop crash in variable type inference (#25696) (Thanks @MartinGC94!) +- Add `PSForEach` and `PSWhere` as aliases for the PowerShell intrinsic methods `Where` and `Foreach` (#25511) (Thanks @powercode!) + +### General Cmdlet Updates and Fixes + +- Remove `IsScreenReaderActive()` check from `ConsoleHost` (#26118) +- Fix `ConvertFrom-Json` to ignore comments inside array literals (#14553) (#26050) (Thanks @MatejKafka!) +- Fix `-Debug` to not trigger the `ShouldProcess` prompt (#26081) +- Add the parameter `Register-ArgumentCompleter -NativeFallback` to support registering a cover-all completer for native commands (#25230) +- Change the default feedback provider timeout from 300ms to 1000ms (#25910) +- Update PATH environment variable for package manager executable on Windows (#25847) +- Fix `Write-Host` to respect `OutputRendering = PlainText` (#21188) +- Improve the `$using` expression support in `Invoke-Command` (#24025) (Thanks @jborean93!) +- Use parameter `HelpMessage` for tool tip in parameter completion (#25108) (Thanks @jborean93!) +- Revert "Never load a module targeting the PSReadLine module's `SessionState`" (#25792) +- Fix debug tracing error with magic extents (#25726) (Thanks @jborean93!) +- Add `MethodInvocation` trace for overload tracing (#21320) (Thanks @jborean93!) +- Improve verbose and debug logging level messaging in web cmdlets (#25510) (Thanks @JustinGrote!) +- Fix quoting in completion if the path includes a double quote character (#25631) (Thanks @MartinGC94!) +- Fix the common parameter `-ProgressAction` for advanced functions (#24591) (Thanks @cmkb3!) +- Use absolute path in `FileSystemProvider.CreateDirectory` (#24615) (Thanks @Tadas!) +- Make inherited protected internal instance members accessible in PowerShell class scope (#25245) (Thanks @mawosoft!) +- Treat `-Target` as literal in `New-Item` (#25186) (Thanks @GameMicrowave!) +- Remove duplicate modules from completion results (#25538) (Thanks @MartinGC94!) +- Add completion for variables assigned in `ArrayLiteralAst` and `ParenExpressionAst` (#25303) (Thanks @MartinGC94!) +- Add support for thousands separators in `[bigint]` casting (#25396) (Thanks @AbishekPonmudi!) +- Add internal methods to check Preferences (#25514) (Thanks @iSazonov!) +- Improve debug logging of Web cmdlet request and response (#25479) (Thanks @JustinGrote!) +- Revert "Allow empty prefix string in 'Import-Module -Prefix' to override default prefix in manifest (#20409)" (#25462) (Thanks @MartinGC94!) +- Fix the `NullReferenceException` when writing progress records to console from multiple threads (#25440) (Thanks @kborowinski!) +- Update `Get-Service` to ignore common errors when retrieving non-critical properties for a service (#24245) (Thanks @jborean93!) +- Add single/double quote support for `Join-String` Argument Completer (#25283) (Thanks @ArmaanMcleod!) +- Fix tab completion for env/function variables (#25346) (Thanks @jborean93!) +- Fix `Out-GridView` by replacing use of obsolete `BinaryFormatter` with custom implementation (#25497) (Thanks @mawosoft!) +- Remove the use of Windows PowerShell ETW provider ID from codebase and update the `PSDiagnostics` module to work for PowerShell 7 (#25590) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @mawosoft, @ArmaanMcleod

+ +
+ +
    +
  • Enable CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types (#25813) (Thanks @xtqqczze!)
  • +
  • Remove some unused ConsoleControl structs (#26063) (Thanks @xtqqczze!)
  • +
  • Remove unused FileStreamBackReader.NativeMethods type (#26062) (Thanks @xtqqczze!)
  • +
  • Ensure data-serialization files end with one newline (#26039) (Thanks @xtqqczze!)
  • +
  • Remove unnecessary CS0618 suppressions from Variant APIs (#26006) (Thanks @xtqqczze!)
  • +
  • Ensure .cs files end with exactly one newline (#25968) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA2105 rule suppression (#25938) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA1703 rule suppression (#25955) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA2240 rule suppression (#25957) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA1701 rule suppression (#25948) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA2233 rule suppression (#25951) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA1026 rule suppression (#25934) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA1059 rule suppression (#25940) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA2118 rule suppression (#25924) (Thanks @xtqqczze!)
  • +
  • Remove redundant System.Runtime.Versioning attributes (#25926) (Thanks @xtqqczze!)
  • +
  • Seal internal types in Microsoft.PowerShell.Commands.Utility (#25892) (Thanks @xtqqczze!)
  • +
  • Seal internal types in Microsoft.PowerShell.Commands.Management (#25849) (Thanks @xtqqczze!)
  • +
  • Make the interface IDeepCloneable internal to minimize confusion (#25552)
  • +
  • Remove OnDeserialized and Serializable attributes from Microsoft.Management.UI.Internal project (#25548)
  • +
  • Refactor Tooltip/ListItemText mapping to use CompletionDisplayInfoMapper delegate (#25395) (Thanks @ArmaanMcleod!)
  • +
+ +
+ +### Tools + +- Add Codeql Suppressions (#25943, #26132) +- Update CODEOWNERS to add Justin as a maintainer (#25386) +- Do not run labels workflow in the internal repository (#25279) + +### Tests + +- Mark the 3 consistently failing tests as pending to unblock PRs (#26091) +- Make some tests less noisy on failure (#26035) (Thanks @xtqqczze!) +- Suppress false positive `PSScriptAnalyzer` warnings in tests and build scripts (#25864) +- Fix updatable help test for new content (#25819) +- Add more tests for `PSForEach` and `PSWhere` methods (#25519) +- Fix the isolated module test that was disabled previously (#25420) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@alerickson, @senerh, @RichardSlater, @xtqqczze

+ +
+ +
    +
  • Update package references for the master branch (#26124)
  • +
  • Remove ThreadJob module and update PSReadLine to 2.4.4-beta4 (#26120)
  • +
  • Automate Store Publishing (#25725)
  • +
  • Add global config change detection to action (#26082)
  • +
  • Update outdated package references (#26069)
  • +
  • Ensure that the workflows are triggered on .globalconfig and other files at the root of the repo (#26034)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.2.0-preview3 (#26056) (Thanks @alerickson!)
  • +
  • Update metadata for Stable to v7.5.3 and LTS to v7.4.12 (#26054) (Thanks @senerh!)
  • +
  • Bump github/codeql-action from 3.30.2 to 3.30.3 (#26036)
  • +
  • Update version for the package Microsoft.PowerShell.Native (#26041)
  • +
  • Fix the APIScan pipeline (#26016)
  • +
  • Move PowerShell build to use .NET SDK 10.0.100-rc.1 (#26027)
  • +
  • fix(apt-package): add libicu76 dependency to support Debian 13 (#25866) (Thanks @RichardSlater!)
  • +
  • Bump github/codeql-action from 3.30.1 to 3.30.2 (#26029)
  • +
  • Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26025)
  • +
  • Bump github/codeql-action from 3.30.0 to 3.30.1 (#26008)
  • +
  • Bump actions/github-script from 7 to 8 (#25983)
  • +
  • Fix variable reference for release environment in pipeline (#26012)
  • +
  • Add LinuxHost Network configuration to PowerShell Packages pipeline (#26000)
  • +
  • Make logical template name consistent between pipelines (#25990)
  • +
  • Update container images to use mcr.microsoft.com for Linux and Azure GǪ (#25981)
  • +
  • Bump github/codeql-action from 3.29.11 to 3.30.0 (#25966)
  • +
  • Bump actions/setup-dotnet from 4 to 5 (#25978)
  • +
  • Add build to vPack Pipeline (#25915)
  • +
  • Replace DOTNET_SKIP_FIRST_TIME_EXPERIENCE with DOTNET_NOLOGO (#25946) (Thanks @xtqqczze!)
  • +
  • Bump actions/dependency-review-action from 4.7.2 to 4.7.3 (#25930)
  • +
  • Bump github/codeql-action from 3.29.10 to 3.29.11 (#25889)
  • +
  • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25885)
  • +
  • Specify .NET Search by Build Type (#25837)
  • +
  • Update PowerShell to use .NET SDK v10-preview.7 (#25876)
  • +
  • Bump actions/dependency-review-action from 4.7.1 to 4.7.2 (#25882)
  • +
  • Bump github/codeql-action from 3.29.9 to 3.29.10 (#25881)
  • +
  • Change the macos runner image to macos 15 large (#25867)
  • +
  • Bump actions/checkout from 4 to 5 (#25853)
  • +
  • Bump github/codeql-action from 3.29.7 to 3.29.9 (#25857)
  • +
  • Update to .NET 10 Preview 6 (#25828)
  • +
  • Bump agrc/create-reminder-action from 1.1.20 to 1.1.22 (#25808)
  • +
  • Bump agrc/reminder-action from 1.0.17 to 1.0.18 (#25807)
  • +
  • Bump github/codeql-action from 3.28.19 to 3.29.5 (#25797)
  • +
  • Bump super-linter/super-linter from 7.4.0 to 8.0.0 (#25770)
  • +
  • Update metadata for v7.5.2 and v7.4.11 releases (#25687)
  • +
  • Correct Capitalization Referencing Templates (#25669)
  • +
  • Change linux packaging tests to ubuntu latest (#25634)
  • +
  • Bump github/codeql-action from 3.28.18 to 3.28.19 (#25636)
  • +
  • Move to .NET 10 preview 4 and update package references (#25602)
  • +
  • Revert "Add windows signing for pwsh.exe" (#25586)
  • +
  • Bump ossf/scorecard-action from 2.4.1 to 2.4.2 (#25628)
  • +
  • Publish .msixbundle package as a VPack (#25612)
  • +
  • Bump agrc/reminder-action from 1.0.16 to 1.0.17 (#25573)
  • +
  • Bump agrc/create-reminder-action from 1.1.18 to 1.1.20 (#25572)
  • +
  • Bump github/codeql-action from 3.28.17 to 3.28.18 (#25580)
  • +
  • Bump super-linter/super-linter from 7.3.0 to 7.4.0 (#25563)
  • +
  • Bump actions/dependency-review-action from 4.7.0 to 4.7.1 (#25562)
  • +
  • Update metadata.json with 7.4.10 (#25554)
  • +
  • Bump github/codeql-action from 3.28.16 to 3.28.17 (#25508)
  • +
  • Bump actions/dependency-review-action from 4.6.0 to 4.7.0 (#25529)
  • +
  • Move MSIXBundle to Packages and Release to GitHub (#25512)
  • +
  • Update outdated package references (#25506)
  • +
  • Bump github/codeql-action from 3.28.15 to 3.28.16 (#25429)
  • +
  • Fix Conditional Parameter to Skip NuGet Publish (#25468)
  • +
  • Update metadata.json (#25438)
  • +
  • Fix MSIX artifact upload, vPack template, changelog hashes, git tag command (#25437)
  • +
  • Use new variables template for vPack (#25434)
  • +
  • Bump agrc/create-reminder-action from 1.1.17 to 1.1.18 (#25416)
  • +
  • Add PSScriptAnalyzer (#25423)
  • +
  • Update outdated package references (#25392)
  • +
  • Use GitHubReleaseTask instead of custom script (#25398)
  • +
  • Update APIScan to use new symbols server (#25388)
  • +
  • Retry ClearlyDefined operations (#25385)
  • +
  • Update to .NET 10.0.100-preview.3 (#25358)
  • +
  • Enhance path filters action to set outputs for all changes when not a PR (#25367)
  • +
  • Combine GitHub and Nuget Release Stage (#25318)
  • +
  • Add Windows Store Signing to MSIX bundle (#25296)
  • +
  • Bump skitionek/notify-microsoft-teams from 190d4d92146df11f854709774a4dae6eaf5e2aa3 to e7a2493ac87dad8aa7a62f079f295e54ff511d88 (#25366)
  • +
  • Add CodeQL suppressions for PowerShell intended behavior (#25359)
  • +
  • Migrate MacOS Signing to OneBranch (#25295)
  • +
  • Bump github/codeql-action from 3.28.13 to 3.28.15 (#25290)
  • +
  • Update test result processing to use NUnitXml format and enhance logging for better clarity (#25288)
  • +
  • Fix R2R for fxdependent packaging (#26131)
  • +
  • Remove UseDotnet task and use the dotnet-install script (#26093)
  • +
+ +
+ +### Documentation and Help Content + +- Fix a typo in the 7.4 changelog (#26038) (Thanks @VbhvGupta!) +- Add 7.4.12 changelog (#26011) +- Add v7.5.3 changelog (#25994) +- Fix typo in changelog for script filename suggestion (#25962) +- Update changelog for v7.5.2 (#25668) +- Update changelog for v7.4.11 (#25667) +- Update build documentation with instruction of dev terminal (#25587) +- Update links and contribution guide in documentation (#25532) (Thanks @JustinGrote!) +- Add 7.4.10 changelog (#25520) +- Add 7.5.1 changelog (#25382) + +[7.6.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.4...v7.6.0-preview.5 + +## [7.6.0-preview.4] + +### Breaking Changes + +- Fix `WildcardPattern.Escape` to escape lone backticks correctly (#25211) (Thanks @ArmaanMcleod!) +- Convert `-ChildPath` parameter to `string[]` for `Join-Path` cmdlet (#24677) (Thanks @ArmaanMcleod!) + +PowerShell 7.6-preview.4 includes the following updated modules: + +- **Microsoft.PowerShell.ThreadJob** v2.2.0 +- **ThreadJob** v2.1.0 +The **ThreadJob** module was renamed to **Microsoft.PowerShell.ThreadJob**. There is no difference +in the functionality of the module. To ensure backward compatibility for scripts that use the old +name, the **ThreadJob** v2.1.0 module is a proxy module that points to the +**Microsoft.PowerShell.ThreadJob** v2.2.0. + +### Engine Updates and Fixes + +- Add `PipelineStopToken` to `Cmdlet` which will be signaled when the pipeline is stopping (#24620) (Thanks @jborean93!) +- Fallback to AppLocker after `WldpCanExecuteFile` (#24912) +- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25022) +- Fix share completion with provider and spaces (#19440) (Thanks @MartinGC94!) + +### General Cmdlet Updates and Fixes + +- Exclude `-OutVariable` assignments within the same `CommandAst` when inferring variables (#25224) (Thanks @MartinGC94!) +- Fix infinite loop in variable type inference (#25206) (Thanks @MartinGC94!) +- Update `Microsoft.PowerShell.PSResourceGet` version in `PSGalleryModules.csproj` (#25135) +- Add tooltips for hashtable key completions (#17864) (Thanks @MartinGC94!) +- Fix type inference of parameters in classic functions (#25172) (Thanks @MartinGC94!) +- Improve assignment type inference (#21143) (Thanks @MartinGC94!) +- Fix `TypeName.GetReflectionType()` to work when the `TypeName` instance represents a generic type definition within a `GenericTypeName` (#24985) +- Remove the old fuzzy suggestion and fix the local script filename suggestion (#25177) +- Improve variable type inference (#19830) (Thanks @MartinGC94!) +- Fix parameter completion when script requirements fail (#17687) (Thanks @MartinGC94!) +- Improve the completion for attribute arguments (#25129) (Thanks @MartinGC94!) +- Fix completion that relies on pseudobinding in script blocks (#25122) (Thanks @MartinGC94!) +- Don't complete duplicate command names (#21113) (Thanks @MartinGC94!) +- Make `SystemPolicy` public APIs visible but non-op on Unix platforms so that they can be included in `PowerShellStandard.Library` (#25051) +- Set standard handles explicitly when starting a process with `-NoNewWindow` (#25061) +- Fix tooltip for variable expansion and include desc (#25112) (Thanks @jborean93!) +- Add type inference for functions without OutputType attribute and anonymous functions (#21127) (Thanks @MartinGC94!) +- Add completion for variables assigned by command redirection (#25104) (Thanks @MartinGC94!) +- Handle type inference for redirected commands (#21131) (Thanks @MartinGC94!) +- Allow empty prefix string in `Import-Module -Prefix` to override default prefix in manifest (#20409) (Thanks @MartinGC94!) +- Update variable/property assignment completion so it can fallback to type inference (#21134) (Thanks @MartinGC94!) +- Use `Get-Help` approach to find `about_*.help.txt` files with correct locale for completions (#24194) (Thanks @MartinGC94!) +- Use script filepath when completing relative paths for using statements (#20017) (Thanks @MartinGC94!) +- Fix completion of variables assigned inside Do loops (#25076) (Thanks @MartinGC94!) +- Fix completion of provider paths when a path returns itself instead of its children (#24755) (Thanks @MartinGC94!) +- Enable completion of scoped variables without specifying scope (#20340) (Thanks @MartinGC94!) +- Fix issue with incomplete results when completing paths with wildcards in non-filesystem providers (#24757) (Thanks @MartinGC94!) +- Allow DSC parsing through OS architecture translation layers (#24852) (Thanks @bdeb1337!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@ArmaanMcleod, @pressRtowin

+ +
+ +
    +
  • Refactor and add comments to CompletionRequiresQuotes to clarify implementation (#25223) (Thanks @ArmaanMcleod!)
  • +
  • Add QuoteCompletionText method to CompletionHelpers class (#25180) (Thanks @ArmaanMcleod!)
  • +
  • Remove CompletionHelpers escape parameter from CompletionRequiresQuotes (#25178) (Thanks @ArmaanMcleod!)
  • +
  • Refactor CompletionHelpers HandleDoubleAndSingleQuote to have less nesting logic (#25179) (Thanks @ArmaanMcleod!)
  • +
  • Make the use of Oxford commas consistent (#25139)(#25140)(Thanks @pressRtowin!)
  • +
  • Move common completion methods to CompletionHelpers class (#25138) (Thanks @ArmaanMcleod!)
  • +
  • Return Array.Empty instead of collection [] (#25137) (Thanks @ArmaanMcleod!)
  • +
+ +
+ +### Tools + +- Check GH token availability for Get-Changelog (#25133) + +### Tests + +- Add XUnit test for `HandleDoubleAndSingleQuote` in CompletionHelpers class (#25181) (Thanks @ArmaanMcleod!) + +### Build and Packaging Improvements + +
+ +
    +
  • Switch to ubuntu-lastest for CI (#25247)
  • +
  • Update outdated package references (#25026)(#25232)
  • +
  • Bump Microsoft.PowerShell.ThreadJob and ThreadJob modules (#25232)
  • +
  • Bump github/codeql-action from 3.27.9 to 3.28.13 (#25218)(#25231)
  • +
  • Update .NET SDK to 10.0.100-preview.2 (#25154)(#25225)
  • +
  • Remove obsolete template from Windows Packaging CI (#25226)
  • +
  • Bump actions/upload-artifact from 4.5.0 to 4.6.2 (#25220)
  • +
  • Bump agrc/reminder-action from 1.0.15 to 1.0.16 (#25222)
  • +
  • Bump actions/checkout from 2 to 4 (#25221)
  • +
  • Add NoWarn NU1605 to System.ServiceModel.* (#25219)
  • +
  • Bump actions/github-script from 6 to 7 (#25217)
  • +
  • Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#25216)
  • +
  • Bump super-linter/super-linter from 7.2.1 to 7.3.0 (#25215)
  • +
  • Bump agrc/create-reminder-action from 1.1.16 to 1.1.17 (#25214)
  • +
  • Remove dependabot updates that don't work (#25213)
  • +
  • Update GitHub Actions to work in private GitHub repo (#25197)
  • +
  • Cleanup old release pipelines (#25201)
  • +
  • Update package pipeline windows image version (#25191)
  • +
  • Skip additional packages when generating component manifest (#25102)
  • +
  • Only build Linux for packaging changes (#25103)
  • +
  • Remove Az module installs and AzureRM uninstalls in pipeline (#25118)
  • +
  • Add GitHub Actions workflow to verify PR labels (#25145)
  • +
  • Add back-port workflow using dotnet/arcade (#25106)
  • +
  • Make Component Manifest Updater use neutral target in addition to RID target (#25094)
  • +
  • Make sure the vPack pipeline does not produce an empty package (#24988)
  • +
+ +
+ +### Documentation and Help Content + +- Add 7.4.9 changelog (#25169) +- Create changelog for 7.4.8 (#25089) + +[7.6.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.3...v7.6.0-preview.4 + +## [7.6.0-preview.3] + +### Breaking Changes + +- Remove trailing space from event source name (#24192) (Thanks @MartinGC94!) + +### General Cmdlet Updates and Fixes + +- Add completion single/double quote support for `-Noun` parameter for `Get-Command` (#24977) (Thanks @ArmaanMcleod!) +- Stringify `ErrorRecord` with empty exception message to empty string (#24949) (Thanks @MatejKafka!) +- Add completion single/double quote support for `-PSEdition` parameter for `Get-Module` (#24971) (Thanks @ArmaanMcleod!) +- Error when `New-Item -Force` is passed an invalid directory name (#24936) (Thanks @kborowinski!) +- Allow `Start-Transcript`to use `$Transcript` which is a `PSObject` wrapped string to specify the transcript path (#24963) (Thanks @kborowinski!) +- Add quote handling in `Verb`, `StrictModeVersion`, `Scope` & `PropertyType` Argument Completers with single helper method (#24839) (Thanks @ArmaanMcleod!) +- Improve `Start-Process -Wait` polling efficiency (#24711) (Thanks @jborean93!) +- Convert `InvalidCommandNameCharacters` in `AnalysisCache` to `SearchValues` for more efficient char searching (#24880) (Thanks @ArmaanMcleod!) +- Convert `s_charactersRequiringQuotes` in Completion Completers to `SearchValues` for more efficient char searching (#24879) (Thanks @ArmaanMcleod!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @fMichaleczek, @ArmaanMcleod

+ +
+ +
    +
  • Fix RunspacePool, RunspacePoolInternal and RemoteRunspacePoolInternal IDisposable implementation (#24720) (Thanks @xtqqczze!)
  • +
  • Remove redundant Attribute suffix (#24940) (Thanks @xtqqczze!)
  • +
  • Fix formatting of the XML comment for SteppablePipeline.Clean() (#24941)
  • +
  • Use Environment.ProcessId in SpecialVariables.PID (#24926) (Thanks @fMichaleczek!)
  • +
  • Replace char[] array in CompletionRequiresQuotes with cached SearchValues (#24907) (Thanks @ArmaanMcleod!)
  • +
  • Update IndexOfAny calls with invalid path/filename to SearchValues<char> for more efficient char searching (#24896) (Thanks @ArmaanMcleod!)
  • +
  • Seal internal types in PlatformInvokes (#24826) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Update CODEOWNERS (#24989) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @KyZy7

+ +
+ +
    +
  • Update branch for release - Transitive - false - none (#24995)
  • +
  • Add setup dotnet action to the build composite action (#24996)
  • +
  • Give the pipeline runs meaningful names (#24987)
  • +
  • Fix V-Pack download package name (#24866)
  • +
  • Set LangVersion compiler option to 13.0 in Test.Common.props (#24621) (Thanks @xtqqczze!)
  • +
  • Fix release branch filters (#24933)
  • +
  • Fix GitHub Action filter overmatching (#24929)
  • +
  • Add UseDotnet task for installing dotnet (#24905)
  • +
  • Convert powershell/PowerShell-CI-macos to GitHub Actions (#24914)
  • +
  • Convert powershell/PowerShell-CI-linux to GitHub Actions (#24913)
  • +
  • Convert powershell/PowerShell-Windows-CI to GitHub Actions (#24899)
  • +
  • Fix MSIX stage in release pipeline (#24900)
  • +
  • Update .NET SDK (#24906)
  • +
  • Update metadata.json (#24862)
  • +
  • PMC parse state correctly from update command's response (#24850)
  • +
  • Add EV2 support for publishing PowerShell packages to PMC (#24841)
  • +
  • Remove AzDO credscan as it is now in GitHub (#24842)
  • +
  • Add *.props and sort path filters for windows CI (#24822)
  • +
  • Use work load identity service connection to download makeappx tool from storage account (#24817)
  • +
  • Update path filters for Windows CI (#24809)
  • +
  • Update outdated package references (#24758)
  • +
  • Update metadata.json (#24787) (Thanks @KyZy7!)
  • +
  • Add tool package download in publish nuget stage (#24790)
  • +
  • Fix Changelog content grab during GitHub Release (#24788)
  • +
  • Update metadata.json (#24764)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
  • +
  • Add a parameter that skips verify packages step (#24763)
  • +
+ +
+ +### Documentation and Help Content + +- Add 7.4.7 Changelog (#24844) +- Create changelog for v7.5.0 (#24808) +- Update Changelog for v7.6.0-preview.2 (#24775) + +[7.6.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.2...v7.6.0-preview.3 + +## [7.6.0-preview.2] - 2025-01-14 + +### General Cmdlet Updates and Fixes + +- Add the `AIShell` module to telemetry collection list (#24747) +- Add helper in `EnumSingleTypeConverter` to get enum names as array (#17785) (Thanks @fflaten!) +- Return correct FileName property for `Get-Item` when listing alternate data streams (#18019) (Thanks @kilasuit!) +- Add `-ExcludeModule` parameter to `Get-Command` (#18955) (Thanks @MartinGC94!) +- Update Named and Statement block type inference to not consider AssignmentStatements and Increment/decrement operators as part of their output (#21137) (Thanks @MartinGC94!) +- Update `DnsNameList` for `X509Certificate2` to use `X509SubjectAlternativeNameExtension.EnumerateDnsNames` Method (#24714) (Thanks @ArmaanMcleod!) +- Add completion of modules by their shortname (#20330) (Thanks @MartinGC94!) +- Fix `Get-ItemProperty` to report non-terminating error for cast exception (#21115) (Thanks @ArmaanMcleod!) +- Add `-PropertyType` argument completer for `New-ItemProperty` (#21117) (Thanks @ArmaanMcleod!) +- Fix a bug in how `Write-Host` handles `XmlNode` object (#24669) (Thanks @brendandburns!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze

+ +
+ +
    +
  • Seal ClientRemoteSessionDSHandlerImpl (#21218) (Thanks @xtqqczze!)
  • +
  • Seal internal type ClientRemoteSessionDSHandlerImpl (#24705) (Thanks @xtqqczze!)
  • +
  • Seal classes in RemotingProtocol2 (#21164) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Added Justin Chung as PowerShell team memeber on releaseTools.psm1 (#24672) + +### Tests + +- Skip CIM ETS member test on older Windows platforms (#24681) + +### Build and Packaging Improvements + +
+ + + +

Updated SDK to 9.0.101

+ +
+ +
    +
  • Update branch for release - Transitive - false - none (#24754)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
  • +
  • Add a parameter that skips verify packages step (#24763)
  • +
  • Make the AssemblyVersion not change for servicing releases (#24667)
  • +
  • Fixed release pipeline errors and switched to KS3 (#24751)
  • +
  • Update outdated package references (#24580)
  • +
  • Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#24689)
  • +
  • Update .NET feed with new domain as azureedge is retiring (#24703)
  • +
  • Bump super-linter/super-linter from 7.2.0 to 7.2.1 (#24678)
  • +
  • Bump github/codeql-action from 3.27.7 to 3.27.9 (#24674)
  • +
  • Bump actions/dependency-review-action from 4.4.0 to 4.5.0 (#24607)
  • +
+ +
+ +### Documentation and Help Content + +- Update cmdlets WG members (#24275) (Thanks @kilasuit!) + +[7.6.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.1...v7.6.0-preview.2 + +## [7.6.0-preview.1] - 2024-12-16 + +### Breaking Changes + +- Treat large Enum values as numbers in `ConvertTo-Json` (#20999) (Thanks @jborean93!) + +### General Cmdlet Updates and Fixes + +- Add proper error for running `Get-PSSession -ComputerName` on Unix (#21009) (Thanks @jborean93!) +- Resolve symbolic link target relative to the symbolic link instead of the working directory (#15235) (#20943) (Thanks @MatejKafka!) +- Fix up buffer management getting network roots (#24600) (Thanks @jborean93!) +- Support `PSObject` wrapped values in `ArgumentToEncodingTransformationAttribute` (#24555) (Thanks @jborean93!) +- Update PSReadLine to 2.3.6 (#24380) +- Add telemetry to track the use of features (#24247) +- Handle global tool specially when prepending `PSHome` to `PATH` (#24228) +- Fix how processor architecture is validated in `Import-Module` (#24265) +- Make features `PSCommandNotFoundSuggestion`, `PSCommandWithArgs`, and `PSModuleAutoLoadSkipOfflineFiles` stable (#24246) +- Write type data to the pipeline instead of collecting it (#24236) (Thanks @MartinGC94!) +- Add support to `Get-Error` to handle BoundParameters (#20640) +- Fix `Get-FormatData` to not cast a type incorrectly (#21157) +- Delay progress bar in `Copy-Item` and `Remove-Item` cmdlets (#24013) (Thanks @TheSpyGod!) +- Add `-Force` parameter to `Resolve-Path` and `Convert-Path` cmdlets to support wildcard hidden files (#20981) (Thanks @ArmaanMcleod!) +- Use host exe to determine `$PSHOME` location when `SMA.dll` location is not found (#24072) +- Fix `Test-ModuleManifest` so it can use a UNC path (#24115) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@eltociear, @JayBazuzi

+ +
+ +
    +
  • Fix typos in ShowModuleControl.xaml.cs (#24248) (Thanks @eltociear!)
  • +
  • Fix a typo in the build doc (#24172) (Thanks @JayBazuzi!)
  • +
+ +
+ +### Tools + +- Fix devcontainer extensions key (#24359) (Thanks @ThomasNieto!) +- Support new backport branch format (#24378) +- Update markdownLink.yml to not run on release branches (#24323) +- Remove old code that downloads msix for win-arm64 (#24175) + +### Tests + +- Fix cleanup in PSResourceGet test (#24339) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@MartinGC94, @jborean93, @xtqqczze, @alerickson, @iSazonov, @rzippo

+ +
+ +
    +
  • Deploy Box update (#24632)
  • +
  • Remove Regex use (#24235) (Thanks @MartinGC94!)
  • +
  • Improve cim ETS member inference completion (#24235) (Thanks @MartinGC94!)
  • +
  • Emit ProgressRecord in CLIXML minishell output (#21373) (Thanks @jborean93!)
  • +
  • Assign the value returned by the MaybeAdd method
  • (#24652) +
  • Add support for interface static abstract props (#21061) (Thanks @jborean93!)
  • +
  • Change call to optional add in the binder expression (#24451) (Thanks @jborean93!)
  • +
  • Turn off AMSI member invocation on nix release builds (#24451) (Thanks @jborean93!)
  • +
  • Bump github/codeql-action from 3.27.0 to 3.27.6 (#24639)
  • +
  • Update src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs (#24239) (Thanks @jborean93!)
  • +
  • Apply suggestions from code review (#24239) (Thanks @jborean93!)
  • +
  • Add remote runspace check for PushRunspace (#24239) (Thanks @jborean93!)
  • +
  • Set LangVersion compiler option to 13.0 (#24619) (Thanks @xtqqczze!)
  • +
  • Set LangVersion compiler option to 13.0 (#24617) (Thanks @xtqqczze!)
  • +
  • Update metadata.json for PowerShell 7.5 RC1 release (#24589)
  • +
  • Update nuget publish to use Deploy Box (#24596)
  • +
  • Added Deploy Box Product Pathway to GitHub Release and NuGet Release Pipelines (#24583)
  • +
  • Update machine pool for copy blob and upload buildinfo stage (#24587)
  • +
  • Bump .NET 9 and dependencies (#24573)
  • +
  • Bump actions/dependency-review-action from 4.3.4 to 4.4.0 (#24503)
  • +
  • Bump actions/checkout from 4.2.1 to 4.2.2 (#24488)
  • +
  • Bump agrc/reminder-action from 1.0.14 to 1.0.15 (#24384)
  • +
  • Bump actions/upload-artifact from 4.4.0 to 4.4.3 (#24410)
  • +
  • Update branch for release (#24534)
  • +
  • Revert "Update package references (#24414)" (#24532)
  • +
  • Add a way to use only NuGet feed sources (#24528)
  • +
  • Update PSResourceGet to v1.1.0-RC2 (#24512) (Thanks @alerickson!)
  • +
  • Bump .NET to 9.0.100-rc.2.24474.11 (#24509)
  • +
  • Fix seed max value for Container Linux CI (#24510)
  • +
  • Update metadata.json for 7.2.24 and 7.4.6 releases (#24484)
  • +
  • Download package from package build for generating vpack (#24481)
  • +
  • Keep the roff file when gzipping it. (#24450)
  • +
  • Delete the msix blob if it's already there (#24353)
  • +
  • Add PMC mapping for debian 12 (bookworm) (#24413)
  • +
  • Checkin generated manpage (#24423)
  • +
  • Add CodeQL scanning to APIScan build (#24303)
  • +
  • Update package references (#24414)
  • +
  • Update vpack pipeline (#24281)
  • +
  • Bring changes from v7.5.0-preview.5 Release Branch to Master (#24369)
  • +
  • Bump agrc/create-reminder-action from 1.1.15 to 1.1.16 (#24375)
  • +
  • Add BaseUrl to buildinfo json file (#24376)
  • +
  • Update metadata.json (#24352)
  • +
  • Copy to static site instead of making blob public (#24269)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (Thanks @alerickson!)
  • +
  • add updated libicu dependency for debian packages (#24301)
  • +
  • add mapping to azurelinux repo (#24290)
  • +
  • Remove the MD5 branch in the strong name signing token calculation (#24288)
  • +
  • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273)
  • +
  • Ensure the official build files CodeQL issues (#24278)
  • +
  • Update experimental-feature json files (#24271)
  • +
  • Make some release tests run in a hosted pools (#24270)
  • +
  • Do not build the exe for Global tool shim project (#24263)
  • +
  • Update and add new NuGet package sources for different environments. (#24264)
  • +
  • Bump skitionek/notify-microsoft-teams (#24261)
  • +
  • Create new pipeline for compliance (#24252)
  • +
  • Capture environment better (#24148)
  • +
  • Add specific path for issues in tsaconfig (#24244)
  • +
  • Use Managed Identity for APIScan authentication (#24243)
  • +
  • Add windows signing for pwsh.exe (#24219)
  • +
  • Bump super-linter/super-linter from 7.0.0 to 7.1.0 (#24223)
  • +
  • Update the URLs used in nuget.config files (#24203)
  • +
  • Check Create and Submit in vPack build by default (#24181)
  • +
  • Replace PSVersion source generator with incremental one (#23815) (Thanks @iSazonov!)
  • +
  • Save man files in /usr/share/man instead of /usr/local/share/man (#23855) (Thanks @rzippo!)
  • +
  • Bump super-linter/super-linter from 6.8.0 to 7.0.0 (#24169)
  • +
+ +
+ +### Documentation and Help Content + +- Updated Third Party Notices (#24666) +- Update `HelpInfoUri` for 7.5 (#24610) +- Update changelog for v7.4.6 release (#24496) +- Update to the latest NOTICES file (#24259) +- Update the changelog `preview.md` (#24213) +- Update changelog readme with 7.4 (#24182) (Thanks @ThomasNieto!) +- Fix Markdown linting error (#24204) +- Updated changelog for v7.2.23 (#24196) (Internal 32131) +- Update changelog and `metadata.json` for v7.4.5 release (#24183) +- Bring 7.2 changelogs back to master (#24158) + +[7.6.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-rc.1...v7.6.0-preview.1 diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index 851640f8cc5..8e97635bada 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,761 +1,2 @@ # Preview Changelog -## [7.6.0-rc.1] - 2026-02-19 - -### Tests - -- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26705) - -### Build and Packaging Improvements - -
- - - -

Expand to see details.

- -
- -
    -
  • Update branch for release (#26779)
  • -
  • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0-rc3 (#26767)
  • -
  • Update Microsoft.PowerShell.Native package version (#26748)
  • -
  • Move PowerShell build to depend on .NET SDK 10.0.102 (#26717)
  • -
  • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26715)
  • -
  • Fix macOS preview package identifier detection to use version string (#26709)
  • -
  • Update metadata.json to update the Latest attribute with a better name (#26708)
  • -
  • Remove unused runCodesignValidationInjection variable from pipeline templates (#26707)
  • -
  • Update Get-ChangeLog to handle backport PRs correctly (#26706)
  • -
  • Bring release changes from the v7.6.0-preview.6 release (#26626)
  • -
  • Fix the DSC test by skipping AfterAll cleanup if the initial setup in BeforeAll failed (#26622)
  • -
- -
- -[7.6.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.6...v7.6.0-rc.1 - -## [7.6.0-preview.6] - 2025-12-11 - -### Engine Updates and Fixes - -- Properly Expand Aliases to their actual ResolvedCommand (#26571) (Thanks @kilasuit!) - -### General Cmdlet Updates and Fixes - -- Update `Microsoft.PowerShell.PSResourceGet` to `v1.2.0-preview5` (#26590) -- Make the experimental feature `PSFeedbackProvider` stable (#26502) -- Fix a regression in the API `CompletionCompleters.CompleteFilename()` that causes null reference exception (#26487) -- Add Delimiter parameter to `Get-Clipboard` (#26572) (Thanks @MartinGC94!) -- Close pipe client handles after creating the child ssh process (#26564) -- Make some experimental features stable (#26490) -- DSC v3 resource for PowerShell Profile (#26447) - -### Tools - -- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26530) -- Add reusable get-changed-files action and refactor existing actions (#26529) -- Refactor analyze job to reusable workflow and enable on Windows CI (#26494) - -### Tests - -- Fix merge conflict checker for empty file lists and filter *.cs files (#26556) -- Add markdown link verification for PRs (#26445) - -### Build and Packaging Improvements - -
- - - -

Expand to see details.

- -
- -
    -
  • Fix template path for rebuild branch check in package.yml (#26560)
  • -
  • Update the macos package name for preview releases to match the previous pattern (#26576)
  • -
  • Add rebuild branch support with conditional MSIX signing (#26573)
  • -
  • Update the WCF packages to the latest version that is compatible with v4.10.3 (#26503)
  • -
  • Improve ADO package build and validation across platforms (#26532)
  • -
  • Mirror .NET/runtime ICU version range in PowerShell (#26563) (Thanks @kasperk81!)
  • -
  • Update the macos package name for preview releases to match the previous pattern (#26562)
  • -
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26561)
  • -
  • Move package validation to package pipeline (#26558)
  • -
  • Optimize/split windows package signing (#26557)
  • -
  • Remove usage of fpm for DEB package generation (#26504)
  • -
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26524)
  • -
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26501)
  • -
  • Replace fpm with native rpmbuild for RPM package generation (#26441)
  • -
  • Fix GitHub API rate limit errors in test actions (#26492)
  • -
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26493)
  • -
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26488)
  • -
  • Fix build to only enable ready-to-run for the Release configuration (#26481)
  • -
  • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26468)
  • -
  • Update outdated package references (#26471)
  • -
  • GitHub Workflow cleanup (#26439)
  • -
  • Update PSResourceGet package version to preview4 (#26438)
  • -
  • Update PSReadLine to v2.4.5 (#26446)
  • -
  • Add network isolation policy parameter to vPack pipeline (#26444)
  • -
  • Fix a couple more lint errors
  • -
  • Fix lint errors in preview.md
  • -
  • Make MSIX publish stage dependent on SetReleaseTagandContainerName stage
  • -
- -
- -[7.6.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.5...v7.6.0-preview.6 - -## [7.6.0-preview.5] - 2025-09-30 - -### Engine Updates and Fixes - -- Allow opt-out of the named-pipe listener using the environment variable `POWERSHELL_DIAGNOSTICS_OPTOUT` (#26086) -- Ensure that socket timeouts are set only during the token validation (#26066) -- Fix race condition in `RemoteHyperVSocket` (#26057) -- Fix `stderr` output of console host to respect `NO_COLOR` (#24391) -- Update PSRP protocol to deprecate session key exchange between newer client and server (#25774) -- Fix the `ssh` PATH check in `SSHConnectionInfo` when the default Runspace is not available (#25780) (Thanks @jborean93!) -- Adding hex format for native command exit codes (#21067) (Thanks @sba923!) -- Fix infinite loop crash in variable type inference (#25696) (Thanks @MartinGC94!) -- Add `PSForEach` and `PSWhere` as aliases for the PowerShell intrinsic methods `Where` and `Foreach` (#25511) (Thanks @powercode!) - -### General Cmdlet Updates and Fixes - -- Remove `IsScreenReaderActive()` check from `ConsoleHost` (#26118) -- Fix `ConvertFrom-Json` to ignore comments inside array literals (#14553) (#26050) (Thanks @MatejKafka!) -- Fix `-Debug` to not trigger the `ShouldProcess` prompt (#26081) -- Add the parameter `Register-ArgumentCompleter -NativeFallback` to support registering a cover-all completer for native commands (#25230) -- Change the default feedback provider timeout from 300ms to 1000ms (#25910) -- Update PATH environment variable for package manager executable on Windows (#25847) -- Fix `Write-Host` to respect `OutputRendering = PlainText` (#21188) -- Improve the `$using` expression support in `Invoke-Command` (#24025) (Thanks @jborean93!) -- Use parameter `HelpMessage` for tool tip in parameter completion (#25108) (Thanks @jborean93!) -- Revert "Never load a module targeting the PSReadLine module's `SessionState`" (#25792) -- Fix debug tracing error with magic extents (#25726) (Thanks @jborean93!) -- Add `MethodInvocation` trace for overload tracing (#21320) (Thanks @jborean93!) -- Improve verbose and debug logging level messaging in web cmdlets (#25510) (Thanks @JustinGrote!) -- Fix quoting in completion if the path includes a double quote character (#25631) (Thanks @MartinGC94!) -- Fix the common parameter `-ProgressAction` for advanced functions (#24591) (Thanks @cmkb3!) -- Use absolute path in `FileSystemProvider.CreateDirectory` (#24615) (Thanks @Tadas!) -- Make inherited protected internal instance members accessible in PowerShell class scope (#25245) (Thanks @mawosoft!) -- Treat `-Target` as literal in `New-Item` (#25186) (Thanks @GameMicrowave!) -- Remove duplicate modules from completion results (#25538) (Thanks @MartinGC94!) -- Add completion for variables assigned in `ArrayLiteralAst` and `ParenExpressionAst` (#25303) (Thanks @MartinGC94!) -- Add support for thousands separators in `[bigint]` casting (#25396) (Thanks @AbishekPonmudi!) -- Add internal methods to check Preferences (#25514) (Thanks @iSazonov!) -- Improve debug logging of Web cmdlet request and response (#25479) (Thanks @JustinGrote!) -- Revert "Allow empty prefix string in 'Import-Module -Prefix' to override default prefix in manifest (#20409)" (#25462) (Thanks @MartinGC94!) -- Fix the `NullReferenceException` when writing progress records to console from multiple threads (#25440) (Thanks @kborowinski!) -- Update `Get-Service` to ignore common errors when retrieving non-critical properties for a service (#24245) (Thanks @jborean93!) -- Add single/double quote support for `Join-String` Argument Completer (#25283) (Thanks @ArmaanMcleod!) -- Fix tab completion for env/function variables (#25346) (Thanks @jborean93!) -- Fix `Out-GridView` by replacing use of obsolete `BinaryFormatter` with custom implementation (#25497) (Thanks @mawosoft!) -- Remove the use of Windows PowerShell ETW provider ID from codebase and update the `PSDiagnostics` module to work for PowerShell 7 (#25590) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@xtqqczze, @mawosoft, @ArmaanMcleod

- -
- -
    -
  • Enable CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types (#25813) (Thanks @xtqqczze!)
  • -
  • Remove some unused ConsoleControl structs (#26063) (Thanks @xtqqczze!)
  • -
  • Remove unused FileStreamBackReader.NativeMethods type (#26062) (Thanks @xtqqczze!)
  • -
  • Ensure data-serialization files end with one newline (#26039) (Thanks @xtqqczze!)
  • -
  • Remove unnecessary CS0618 suppressions from Variant APIs (#26006) (Thanks @xtqqczze!)
  • -
  • Ensure .cs files end with exactly one newline (#25968) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA2105 rule suppression (#25938) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA1703 rule suppression (#25955) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA2240 rule suppression (#25957) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA1701 rule suppression (#25948) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA2233 rule suppression (#25951) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA1026 rule suppression (#25934) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA1059 rule suppression (#25940) (Thanks @xtqqczze!)
  • -
  • Remove obsolete CA2118 rule suppression (#25924) (Thanks @xtqqczze!)
  • -
  • Remove redundant System.Runtime.Versioning attributes (#25926) (Thanks @xtqqczze!)
  • -
  • Seal internal types in Microsoft.PowerShell.Commands.Utility (#25892) (Thanks @xtqqczze!)
  • -
  • Seal internal types in Microsoft.PowerShell.Commands.Management (#25849) (Thanks @xtqqczze!)
  • -
  • Make the interface IDeepCloneable internal to minimize confusion (#25552)
  • -
  • Remove OnDeserialized and Serializable attributes from Microsoft.Management.UI.Internal project (#25548)
  • -
  • Refactor Tooltip/ListItemText mapping to use CompletionDisplayInfoMapper delegate (#25395) (Thanks @ArmaanMcleod!)
  • -
- -
- -### Tools - -- Add Codeql Suppressions (#25943, #26132) -- Update CODEOWNERS to add Justin as a maintainer (#25386) -- Do not run labels workflow in the internal repository (#25279) - -### Tests - -- Mark the 3 consistently failing tests as pending to unblock PRs (#26091) -- Make some tests less noisy on failure (#26035) (Thanks @xtqqczze!) -- Suppress false positive `PSScriptAnalyzer` warnings in tests and build scripts (#25864) -- Fix updatable help test for new content (#25819) -- Add more tests for `PSForEach` and `PSWhere` methods (#25519) -- Fix the isolated module test that was disabled previously (#25420) - -### Build and Packaging Improvements - -
- - - -

We thank the following contributors!

-

@alerickson, @senerh, @RichardSlater, @xtqqczze

- -
- -
    -
  • Update package references for the master branch (#26124)
  • -
  • Remove ThreadJob module and update PSReadLine to 2.4.4-beta4 (#26120)
  • -
  • Automate Store Publishing (#25725)
  • -
  • Add global config change detection to action (#26082)
  • -
  • Update outdated package references (#26069)
  • -
  • Ensure that the workflows are triggered on .globalconfig and other files at the root of the repo (#26034)
  • -
  • Update Microsoft.PowerShell.PSResourceGet to 1.2.0-preview3 (#26056) (Thanks @alerickson!)
  • -
  • Update metadata for Stable to v7.5.3 and LTS to v7.4.12 (#26054) (Thanks @senerh!)
  • -
  • Bump github/codeql-action from 3.30.2 to 3.30.3 (#26036)
  • -
  • Update version for the package Microsoft.PowerShell.Native (#26041)
  • -
  • Fix the APIScan pipeline (#26016)
  • -
  • Move PowerShell build to use .NET SDK 10.0.100-rc.1 (#26027)
  • -
  • fix(apt-package): add libicu76 dependency to support Debian 13 (#25866) (Thanks @RichardSlater!)
  • -
  • Bump github/codeql-action from 3.30.1 to 3.30.2 (#26029)
  • -
  • Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26025)
  • -
  • Bump github/codeql-action from 3.30.0 to 3.30.1 (#26008)
  • -
  • Bump actions/github-script from 7 to 8 (#25983)
  • -
  • Fix variable reference for release environment in pipeline (#26012)
  • -
  • Add LinuxHost Network configuration to PowerShell Packages pipeline (#26000)
  • -
  • Make logical template name consistent between pipelines (#25990)
  • -
  • Update container images to use mcr.microsoft.com for Linux and Azure GǪ (#25981)
  • -
  • Bump github/codeql-action from 3.29.11 to 3.30.0 (#25966)
  • -
  • Bump actions/setup-dotnet from 4 to 5 (#25978)
  • -
  • Add build to vPack Pipeline (#25915)
  • -
  • Replace DOTNET_SKIP_FIRST_TIME_EXPERIENCE with DOTNET_NOLOGO (#25946) (Thanks @xtqqczze!)
  • -
  • Bump actions/dependency-review-action from 4.7.2 to 4.7.3 (#25930)
  • -
  • Bump github/codeql-action from 3.29.10 to 3.29.11 (#25889)
  • -
  • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25885)
  • -
  • Specify .NET Search by Build Type (#25837)
  • -
  • Update PowerShell to use .NET SDK v10-preview.7 (#25876)
  • -
  • Bump actions/dependency-review-action from 4.7.1 to 4.7.2 (#25882)
  • -
  • Bump github/codeql-action from 3.29.9 to 3.29.10 (#25881)
  • -
  • Change the macos runner image to macos 15 large (#25867)
  • -
  • Bump actions/checkout from 4 to 5 (#25853)
  • -
  • Bump github/codeql-action from 3.29.7 to 3.29.9 (#25857)
  • -
  • Update to .NET 10 Preview 6 (#25828)
  • -
  • Bump agrc/create-reminder-action from 1.1.20 to 1.1.22 (#25808)
  • -
  • Bump agrc/reminder-action from 1.0.17 to 1.0.18 (#25807)
  • -
  • Bump github/codeql-action from 3.28.19 to 3.29.5 (#25797)
  • -
  • Bump super-linter/super-linter from 7.4.0 to 8.0.0 (#25770)
  • -
  • Update metadata for v7.5.2 and v7.4.11 releases (#25687)
  • -
  • Correct Capitalization Referencing Templates (#25669)
  • -
  • Change linux packaging tests to ubuntu latest (#25634)
  • -
  • Bump github/codeql-action from 3.28.18 to 3.28.19 (#25636)
  • -
  • Move to .NET 10 preview 4 and update package references (#25602)
  • -
  • Revert "Add windows signing for pwsh.exe" (#25586)
  • -
  • Bump ossf/scorecard-action from 2.4.1 to 2.4.2 (#25628)
  • -
  • Publish .msixbundle package as a VPack (#25612)
  • -
  • Bump agrc/reminder-action from 1.0.16 to 1.0.17 (#25573)
  • -
  • Bump agrc/create-reminder-action from 1.1.18 to 1.1.20 (#25572)
  • -
  • Bump github/codeql-action from 3.28.17 to 3.28.18 (#25580)
  • -
  • Bump super-linter/super-linter from 7.3.0 to 7.4.0 (#25563)
  • -
  • Bump actions/dependency-review-action from 4.7.0 to 4.7.1 (#25562)
  • -
  • Update metadata.json with 7.4.10 (#25554)
  • -
  • Bump github/codeql-action from 3.28.16 to 3.28.17 (#25508)
  • -
  • Bump actions/dependency-review-action from 4.6.0 to 4.7.0 (#25529)
  • -
  • Move MSIXBundle to Packages and Release to GitHub (#25512)
  • -
  • Update outdated package references (#25506)
  • -
  • Bump github/codeql-action from 3.28.15 to 3.28.16 (#25429)
  • -
  • Fix Conditional Parameter to Skip NuGet Publish (#25468)
  • -
  • Update metadata.json (#25438)
  • -
  • Fix MSIX artifact upload, vPack template, changelog hashes, git tag command (#25437)
  • -
  • Use new variables template for vPack (#25434)
  • -
  • Bump agrc/create-reminder-action from 1.1.17 to 1.1.18 (#25416)
  • -
  • Add PSScriptAnalyzer (#25423)
  • -
  • Update outdated package references (#25392)
  • -
  • Use GitHubReleaseTask instead of custom script (#25398)
  • -
  • Update APIScan to use new symbols server (#25388)
  • -
  • Retry ClearlyDefined operations (#25385)
  • -
  • Update to .NET 10.0.100-preview.3 (#25358)
  • -
  • Enhance path filters action to set outputs for all changes when not a PR (#25367)
  • -
  • Combine GitHub and Nuget Release Stage (#25318)
  • -
  • Add Windows Store Signing to MSIX bundle (#25296)
  • -
  • Bump skitionek/notify-microsoft-teams from 190d4d92146df11f854709774a4dae6eaf5e2aa3 to e7a2493ac87dad8aa7a62f079f295e54ff511d88 (#25366)
  • -
  • Add CodeQL suppressions for PowerShell intended behavior (#25359)
  • -
  • Migrate MacOS Signing to OneBranch (#25295)
  • -
  • Bump github/codeql-action from 3.28.13 to 3.28.15 (#25290)
  • -
  • Update test result processing to use NUnitXml format and enhance logging for better clarity (#25288)
  • -
  • Fix R2R for fxdependent packaging (#26131)
  • -
  • Remove UseDotnet task and use the dotnet-install script (#26093)
  • -
- -
- -### Documentation and Help Content - -- Fix a typo in the 7.4 changelog (#26038) (Thanks @VbhvGupta!) -- Add 7.4.12 changelog (#26011) -- Add v7.5.3 changelog (#25994) -- Fix typo in changelog for script filename suggestion (#25962) -- Update changelog for v7.5.2 (#25668) -- Update changelog for v7.4.11 (#25667) -- Update build documentation with instruction of dev terminal (#25587) -- Update links and contribution guide in documentation (#25532) (Thanks @JustinGrote!) -- Add 7.4.10 changelog (#25520) -- Add 7.5.1 changelog (#25382) - -[7.6.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.4...v7.6.0-preview.5 - -## [7.6.0-preview.4] - -### Breaking Changes - -- Fix `WildcardPattern.Escape` to escape lone backticks correctly (#25211) (Thanks @ArmaanMcleod!) -- Convert `-ChildPath` parameter to `string[]` for `Join-Path` cmdlet (#24677) (Thanks @ArmaanMcleod!) - -PowerShell 7.6-preview.4 includes the following updated modules: - -- **Microsoft.PowerShell.ThreadJob** v2.2.0 -- **ThreadJob** v2.1.0 -The **ThreadJob** module was renamed to **Microsoft.PowerShell.ThreadJob**. There is no difference -in the functionality of the module. To ensure backward compatibility for scripts that use the old -name, the **ThreadJob** v2.1.0 module is a proxy module that points to the -**Microsoft.PowerShell.ThreadJob** v2.2.0. - -### Engine Updates and Fixes - -- Add `PipelineStopToken` to `Cmdlet` which will be signaled when the pipeline is stopping (#24620) (Thanks @jborean93!) -- Fallback to AppLocker after `WldpCanExecuteFile` (#24912) -- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25022) -- Fix share completion with provider and spaces (#19440) (Thanks @MartinGC94!) - -### General Cmdlet Updates and Fixes - -- Exclude `-OutVariable` assignments within the same `CommandAst` when inferring variables (#25224) (Thanks @MartinGC94!) -- Fix infinite loop in variable type inference (#25206) (Thanks @MartinGC94!) -- Update `Microsoft.PowerShell.PSResourceGet` version in `PSGalleryModules.csproj` (#25135) -- Add tooltips for hashtable key completions (#17864) (Thanks @MartinGC94!) -- Fix type inference of parameters in classic functions (#25172) (Thanks @MartinGC94!) -- Improve assignment type inference (#21143) (Thanks @MartinGC94!) -- Fix `TypeName.GetReflectionType()` to work when the `TypeName` instance represents a generic type definition within a `GenericTypeName` (#24985) -- Remove the old fuzzy suggestion and fix the local script filename suggestion (#25177) -- Improve variable type inference (#19830) (Thanks @MartinGC94!) -- Fix parameter completion when script requirements fail (#17687) (Thanks @MartinGC94!) -- Improve the completion for attribute arguments (#25129) (Thanks @MartinGC94!) -- Fix completion that relies on pseudobinding in script blocks (#25122) (Thanks @MartinGC94!) -- Don't complete duplicate command names (#21113) (Thanks @MartinGC94!) -- Make `SystemPolicy` public APIs visible but non-op on Unix platforms so that they can be included in `PowerShellStandard.Library` (#25051) -- Set standard handles explicitly when starting a process with `-NoNewWindow` (#25061) -- Fix tooltip for variable expansion and include desc (#25112) (Thanks @jborean93!) -- Add type inference for functions without OutputType attribute and anonymous functions (#21127) (Thanks @MartinGC94!) -- Add completion for variables assigned by command redirection (#25104) (Thanks @MartinGC94!) -- Handle type inference for redirected commands (#21131) (Thanks @MartinGC94!) -- Allow empty prefix string in `Import-Module -Prefix` to override default prefix in manifest (#20409) (Thanks @MartinGC94!) -- Update variable/property assignment completion so it can fallback to type inference (#21134) (Thanks @MartinGC94!) -- Use `Get-Help` approach to find `about_*.help.txt` files with correct locale for completions (#24194) (Thanks @MartinGC94!) -- Use script filepath when completing relative paths for using statements (#20017) (Thanks @MartinGC94!) -- Fix completion of variables assigned inside Do loops (#25076) (Thanks @MartinGC94!) -- Fix completion of provider paths when a path returns itself instead of its children (#24755) (Thanks @MartinGC94!) -- Enable completion of scoped variables without specifying scope (#20340) (Thanks @MartinGC94!) -- Fix issue with incomplete results when completing paths with wildcards in non-filesystem providers (#24757) (Thanks @MartinGC94!) -- Allow DSC parsing through OS architecture translation layers (#24852) (Thanks @bdeb1337!) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@ArmaanMcleod, @pressRtowin

- -
- -
    -
  • Refactor and add comments to CompletionRequiresQuotes to clarify implementation (#25223) (Thanks @ArmaanMcleod!)
  • -
  • Add QuoteCompletionText method to CompletionHelpers class (#25180) (Thanks @ArmaanMcleod!)
  • -
  • Remove CompletionHelpers escape parameter from CompletionRequiresQuotes (#25178) (Thanks @ArmaanMcleod!)
  • -
  • Refactor CompletionHelpers HandleDoubleAndSingleQuote to have less nesting logic (#25179) (Thanks @ArmaanMcleod!)
  • -
  • Make the use of Oxford commas consistent (#25139)(#25140)(Thanks @pressRtowin!)
  • -
  • Move common completion methods to CompletionHelpers class (#25138) (Thanks @ArmaanMcleod!)
  • -
  • Return Array.Empty instead of collection [] (#25137) (Thanks @ArmaanMcleod!)
  • -
- -
- -### Tools - -- Check GH token availability for Get-Changelog (#25133) - -### Tests - -- Add XUnit test for `HandleDoubleAndSingleQuote` in CompletionHelpers class (#25181) (Thanks @ArmaanMcleod!) - -### Build and Packaging Improvements - -
- -
    -
  • Switch to ubuntu-lastest for CI (#25247)
  • -
  • Update outdated package references (#25026)(#25232)
  • -
  • Bump Microsoft.PowerShell.ThreadJob and ThreadJob modules (#25232)
  • -
  • Bump github/codeql-action from 3.27.9 to 3.28.13 (#25218)(#25231)
  • -
  • Update .NET SDK to 10.0.100-preview.2 (#25154)(#25225)
  • -
  • Remove obsolete template from Windows Packaging CI (#25226)
  • -
  • Bump actions/upload-artifact from 4.5.0 to 4.6.2 (#25220)
  • -
  • Bump agrc/reminder-action from 1.0.15 to 1.0.16 (#25222)
  • -
  • Bump actions/checkout from 2 to 4 (#25221)
  • -
  • Add NoWarn NU1605 to System.ServiceModel.* (#25219)
  • -
  • Bump actions/github-script from 6 to 7 (#25217)
  • -
  • Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#25216)
  • -
  • Bump super-linter/super-linter from 7.2.1 to 7.3.0 (#25215)
  • -
  • Bump agrc/create-reminder-action from 1.1.16 to 1.1.17 (#25214)
  • -
  • Remove dependabot updates that don't work (#25213)
  • -
  • Update GitHub Actions to work in private GitHub repo (#25197)
  • -
  • Cleanup old release pipelines (#25201)
  • -
  • Update package pipeline windows image version (#25191)
  • -
  • Skip additional packages when generating component manifest (#25102)
  • -
  • Only build Linux for packaging changes (#25103)
  • -
  • Remove Az module installs and AzureRM uninstalls in pipeline (#25118)
  • -
  • Add GitHub Actions workflow to verify PR labels (#25145)
  • -
  • Add back-port workflow using dotnet/arcade (#25106)
  • -
  • Make Component Manifest Updater use neutral target in addition to RID target (#25094)
  • -
  • Make sure the vPack pipeline does not produce an empty package (#24988)
  • -
- -
- -### Documentation and Help Content - -- Add 7.4.9 changelog (#25169) -- Create changelog for 7.4.8 (#25089) - -[7.6.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.3...v7.6.0-preview.4 - -## [7.6.0-preview.3] - -### Breaking Changes - -- Remove trailing space from event source name (#24192) (Thanks @MartinGC94!) - -### General Cmdlet Updates and Fixes - -- Add completion single/double quote support for `-Noun` parameter for `Get-Command` (#24977) (Thanks @ArmaanMcleod!) -- Stringify `ErrorRecord` with empty exception message to empty string (#24949) (Thanks @MatejKafka!) -- Add completion single/double quote support for `-PSEdition` parameter for `Get-Module` (#24971) (Thanks @ArmaanMcleod!) -- Error when `New-Item -Force` is passed an invalid directory name (#24936) (Thanks @kborowinski!) -- Allow `Start-Transcript`to use `$Transcript` which is a `PSObject` wrapped string to specify the transcript path (#24963) (Thanks @kborowinski!) -- Add quote handling in `Verb`, `StrictModeVersion`, `Scope` & `PropertyType` Argument Completers with single helper method (#24839) (Thanks @ArmaanMcleod!) -- Improve `Start-Process -Wait` polling efficiency (#24711) (Thanks @jborean93!) -- Convert `InvalidCommandNameCharacters` in `AnalysisCache` to `SearchValues` for more efficient char searching (#24880) (Thanks @ArmaanMcleod!) -- Convert `s_charactersRequiringQuotes` in Completion Completers to `SearchValues` for more efficient char searching (#24879) (Thanks @ArmaanMcleod!) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@xtqqczze, @fMichaleczek, @ArmaanMcleod

- -
- -
    -
  • Fix RunspacePool, RunspacePoolInternal and RemoteRunspacePoolInternal IDisposable implementation (#24720) (Thanks @xtqqczze!)
  • -
  • Remove redundant Attribute suffix (#24940) (Thanks @xtqqczze!)
  • -
  • Fix formatting of the XML comment for SteppablePipeline.Clean() (#24941)
  • -
  • Use Environment.ProcessId in SpecialVariables.PID (#24926) (Thanks @fMichaleczek!)
  • -
  • Replace char[] array in CompletionRequiresQuotes with cached SearchValues (#24907) (Thanks @ArmaanMcleod!)
  • -
  • Update IndexOfAny calls with invalid path/filename to SearchValues<char> for more efficient char searching (#24896) (Thanks @ArmaanMcleod!)
  • -
  • Seal internal types in PlatformInvokes (#24826) (Thanks @xtqqczze!)
  • -
- -
- -### Tools - -- Update CODEOWNERS (#24989) - -### Build and Packaging Improvements - -
- - - -

We thank the following contributors!

-

@xtqqczze, @KyZy7

- -
- -
    -
  • Update branch for release - Transitive - false - none (#24995)
  • -
  • Add setup dotnet action to the build composite action (#24996)
  • -
  • Give the pipeline runs meaningful names (#24987)
  • -
  • Fix V-Pack download package name (#24866)
  • -
  • Set LangVersion compiler option to 13.0 in Test.Common.props (#24621) (Thanks @xtqqczze!)
  • -
  • Fix release branch filters (#24933)
  • -
  • Fix GitHub Action filter overmatching (#24929)
  • -
  • Add UseDotnet task for installing dotnet (#24905)
  • -
  • Convert powershell/PowerShell-CI-macos to GitHub Actions (#24914)
  • -
  • Convert powershell/PowerShell-CI-linux to GitHub Actions (#24913)
  • -
  • Convert powershell/PowerShell-Windows-CI to GitHub Actions (#24899)
  • -
  • Fix MSIX stage in release pipeline (#24900)
  • -
  • Update .NET SDK (#24906)
  • -
  • Update metadata.json (#24862)
  • -
  • PMC parse state correctly from update command's response (#24850)
  • -
  • Add EV2 support for publishing PowerShell packages to PMC (#24841)
  • -
  • Remove AzDO credscan as it is now in GitHub (#24842)
  • -
  • Add *.props and sort path filters for windows CI (#24822)
  • -
  • Use work load identity service connection to download makeappx tool from storage account (#24817)
  • -
  • Update path filters for Windows CI (#24809)
  • -
  • Update outdated package references (#24758)
  • -
  • Update metadata.json (#24787) (Thanks @KyZy7!)
  • -
  • Add tool package download in publish nuget stage (#24790)
  • -
  • Fix Changelog content grab during GitHub Release (#24788)
  • -
  • Update metadata.json (#24764)
  • -
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
  • -
  • Add a parameter that skips verify packages step (#24763)
  • -
- -
- -### Documentation and Help Content - -- Add 7.4.7 Changelog (#24844) -- Create changelog for v7.5.0 (#24808) -- Update Changelog for v7.6.0-preview.2 (#24775) - -[7.6.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.2...v7.6.0-preview.3 - -## [7.6.0-preview.2] - 2025-01-14 - -### General Cmdlet Updates and Fixes - -- Add the `AIShell` module to telemetry collection list (#24747) -- Add helper in `EnumSingleTypeConverter` to get enum names as array (#17785) (Thanks @fflaten!) -- Return correct FileName property for `Get-Item` when listing alternate data streams (#18019) (Thanks @kilasuit!) -- Add `-ExcludeModule` parameter to `Get-Command` (#18955) (Thanks @MartinGC94!) -- Update Named and Statement block type inference to not consider AssignmentStatements and Increment/decrement operators as part of their output (#21137) (Thanks @MartinGC94!) -- Update `DnsNameList` for `X509Certificate2` to use `X509SubjectAlternativeNameExtension.EnumerateDnsNames` Method (#24714) (Thanks @ArmaanMcleod!) -- Add completion of modules by their shortname (#20330) (Thanks @MartinGC94!) -- Fix `Get-ItemProperty` to report non-terminating error for cast exception (#21115) (Thanks @ArmaanMcleod!) -- Add `-PropertyType` argument completer for `New-ItemProperty` (#21117) (Thanks @ArmaanMcleod!) -- Fix a bug in how `Write-Host` handles `XmlNode` object (#24669) (Thanks @brendandburns!) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@xtqqczze

- -
- -
    -
  • Seal ClientRemoteSessionDSHandlerImpl (#21218) (Thanks @xtqqczze!)
  • -
  • Seal internal type ClientRemoteSessionDSHandlerImpl (#24705) (Thanks @xtqqczze!)
  • -
  • Seal classes in RemotingProtocol2 (#21164) (Thanks @xtqqczze!)
  • -
- -
- -### Tools - -- Added Justin Chung as PowerShell team memeber on releaseTools.psm1 (#24672) - -### Tests - -- Skip CIM ETS member test on older Windows platforms (#24681) - -### Build and Packaging Improvements - -
- - - -

Updated SDK to 9.0.101

- -
- -
    -
  • Update branch for release - Transitive - false - none (#24754)
  • -
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
  • -
  • Add a parameter that skips verify packages step (#24763)
  • -
  • Make the AssemblyVersion not change for servicing releases (#24667)
  • -
  • Fixed release pipeline errors and switched to KS3 (#24751)
  • -
  • Update outdated package references (#24580)
  • -
  • Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#24689)
  • -
  • Update .NET feed with new domain as azureedge is retiring (#24703)
  • -
  • Bump super-linter/super-linter from 7.2.0 to 7.2.1 (#24678)
  • -
  • Bump github/codeql-action from 3.27.7 to 3.27.9 (#24674)
  • -
  • Bump actions/dependency-review-action from 4.4.0 to 4.5.0 (#24607)
  • -
- -
- -### Documentation and Help Content - -- Update cmdlets WG members (#24275) (Thanks @kilasuit!) - -[7.6.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.1...v7.6.0-preview.2 - -## [7.6.0-preview.1] - 2024-12-16 - -### Breaking Changes - -- Treat large Enum values as numbers in `ConvertTo-Json` (#20999) (Thanks @jborean93!) - -### General Cmdlet Updates and Fixes - -- Add proper error for running `Get-PSSession -ComputerName` on Unix (#21009) (Thanks @jborean93!) -- Resolve symbolic link target relative to the symbolic link instead of the working directory (#15235) (#20943) (Thanks @MatejKafka!) -- Fix up buffer management getting network roots (#24600) (Thanks @jborean93!) -- Support `PSObject` wrapped values in `ArgumentToEncodingTransformationAttribute` (#24555) (Thanks @jborean93!) -- Update PSReadLine to 2.3.6 (#24380) -- Add telemetry to track the use of features (#24247) -- Handle global tool specially when prepending `PSHome` to `PATH` (#24228) -- Fix how processor architecture is validated in `Import-Module` (#24265) -- Make features `PSCommandNotFoundSuggestion`, `PSCommandWithArgs`, and `PSModuleAutoLoadSkipOfflineFiles` stable (#24246) -- Write type data to the pipeline instead of collecting it (#24236) (Thanks @MartinGC94!) -- Add support to `Get-Error` to handle BoundParameters (#20640) -- Fix `Get-FormatData` to not cast a type incorrectly (#21157) -- Delay progress bar in `Copy-Item` and `Remove-Item` cmdlets (#24013) (Thanks @TheSpyGod!) -- Add `-Force` parameter to `Resolve-Path` and `Convert-Path` cmdlets to support wildcard hidden files (#20981) (Thanks @ArmaanMcleod!) -- Use host exe to determine `$PSHOME` location when `SMA.dll` location is not found (#24072) -- Fix `Test-ModuleManifest` so it can use a UNC path (#24115) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@eltociear, @JayBazuzi

- -
- -
    -
  • Fix typos in ShowModuleControl.xaml.cs (#24248) (Thanks @eltociear!)
  • -
  • Fix a typo in the build doc (#24172) (Thanks @JayBazuzi!)
  • -
- -
- -### Tools - -- Fix devcontainer extensions key (#24359) (Thanks @ThomasNieto!) -- Support new backport branch format (#24378) -- Update markdownLink.yml to not run on release branches (#24323) -- Remove old code that downloads msix for win-arm64 (#24175) - -### Tests - -- Fix cleanup in PSResourceGet test (#24339) - -### Build and Packaging Improvements - -
- - - -

We thank the following contributors!

-

@MartinGC94, @jborean93, @xtqqczze, @alerickson, @iSazonov, @rzippo

- -
- -
    -
  • Deploy Box update (#24632)
  • -
  • Remove Regex use (#24235) (Thanks @MartinGC94!)
  • -
  • Improve cim ETS member inference completion (#24235) (Thanks @MartinGC94!)
  • -
  • Emit ProgressRecord in CLIXML minishell output (#21373) (Thanks @jborean93!)
  • -
  • Assign the value returned by the MaybeAdd method
  • (#24652) -
  • Add support for interface static abstract props (#21061) (Thanks @jborean93!)
  • -
  • Change call to optional add in the binder expression (#24451) (Thanks @jborean93!)
  • -
  • Turn off AMSI member invocation on nix release builds (#24451) (Thanks @jborean93!)
  • -
  • Bump github/codeql-action from 3.27.0 to 3.27.6 (#24639)
  • -
  • Update src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs (#24239) (Thanks @jborean93!)
  • -
  • Apply suggestions from code review (#24239) (Thanks @jborean93!)
  • -
  • Add remote runspace check for PushRunspace (#24239) (Thanks @jborean93!)
  • -
  • Set LangVersion compiler option to 13.0 (#24619) (Thanks @xtqqczze!)
  • -
  • Set LangVersion compiler option to 13.0 (#24617) (Thanks @xtqqczze!)
  • -
  • Update metadata.json for PowerShell 7.5 RC1 release (#24589)
  • -
  • Update nuget publish to use Deploy Box (#24596)
  • -
  • Added Deploy Box Product Pathway to GitHub Release and NuGet Release Pipelines (#24583)
  • -
  • Update machine pool for copy blob and upload buildinfo stage (#24587)
  • -
  • Bump .NET 9 and dependencies (#24573)
  • -
  • Bump actions/dependency-review-action from 4.3.4 to 4.4.0 (#24503)
  • -
  • Bump actions/checkout from 4.2.1 to 4.2.2 (#24488)
  • -
  • Bump agrc/reminder-action from 1.0.14 to 1.0.15 (#24384)
  • -
  • Bump actions/upload-artifact from 4.4.0 to 4.4.3 (#24410)
  • -
  • Update branch for release (#24534)
  • -
  • Revert "Update package references (#24414)" (#24532)
  • -
  • Add a way to use only NuGet feed sources (#24528)
  • -
  • Update PSResourceGet to v1.1.0-RC2 (#24512) (Thanks @alerickson!)
  • -
  • Bump .NET to 9.0.100-rc.2.24474.11 (#24509)
  • -
  • Fix seed max value for Container Linux CI (#24510)
  • -
  • Update metadata.json for 7.2.24 and 7.4.6 releases (#24484)
  • -
  • Download package from package build for generating vpack (#24481)
  • -
  • Keep the roff file when gzipping it. (#24450)
  • -
  • Delete the msix blob if it's already there (#24353)
  • -
  • Add PMC mapping for debian 12 (bookworm) (#24413)
  • -
  • Checkin generated manpage (#24423)
  • -
  • Add CodeQL scanning to APIScan build (#24303)
  • -
  • Update package references (#24414)
  • -
  • Update vpack pipeline (#24281)
  • -
  • Bring changes from v7.5.0-preview.5 Release Branch to Master (#24369)
  • -
  • Bump agrc/create-reminder-action from 1.1.15 to 1.1.16 (#24375)
  • -
  • Add BaseUrl to buildinfo json file (#24376)
  • -
  • Update metadata.json (#24352)
  • -
  • Copy to static site instead of making blob public (#24269)
  • -
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (Thanks @alerickson!)
  • -
  • add updated libicu dependency for debian packages (#24301)
  • -
  • add mapping to azurelinux repo (#24290)
  • -
  • Remove the MD5 branch in the strong name signing token calculation (#24288)
  • -
  • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273)
  • -
  • Ensure the official build files CodeQL issues (#24278)
  • -
  • Update experimental-feature json files (#24271)
  • -
  • Make some release tests run in a hosted pools (#24270)
  • -
  • Do not build the exe for Global tool shim project (#24263)
  • -
  • Update and add new NuGet package sources for different environments. (#24264)
  • -
  • Bump skitionek/notify-microsoft-teams (#24261)
  • -
  • Create new pipeline for compliance (#24252)
  • -
  • Capture environment better (#24148)
  • -
  • Add specific path for issues in tsaconfig (#24244)
  • -
  • Use Managed Identity for APIScan authentication (#24243)
  • -
  • Add windows signing for pwsh.exe (#24219)
  • -
  • Bump super-linter/super-linter from 7.0.0 to 7.1.0 (#24223)
  • -
  • Update the URLs used in nuget.config files (#24203)
  • -
  • Check Create and Submit in vPack build by default (#24181)
  • -
  • Replace PSVersion source generator with incremental one (#23815) (Thanks @iSazonov!)
  • -
  • Save man files in /usr/share/man instead of /usr/local/share/man (#23855) (Thanks @rzippo!)
  • -
  • Bump super-linter/super-linter from 6.8.0 to 7.0.0 (#24169)
  • -
- -
- -### Documentation and Help Content - -- Updated Third Party Notices (#24666) -- Update `HelpInfoUri` for 7.5 (#24610) -- Update changelog for v7.4.6 release (#24496) -- Update to the latest NOTICES file (#24259) -- Update the changelog `preview.md` (#24213) -- Update changelog readme with 7.4 (#24182) (Thanks @ThomasNieto!) -- Fix Markdown linting error (#24204) -- Updated changelog for v7.2.23 (#24196) (Internal 32131) -- Update changelog and `metadata.json` for v7.4.5 release (#24183) -- Bring 7.2 changelogs back to master (#24158) - -[7.6.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-rc.1...v7.6.0-preview.1 From 8947ed62e6293a425a5b97e4d9791476ea7bf30c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:58:55 +0000 Subject: [PATCH 229/255] release-upload-buildinfo: replace version-comparison channel gating with metadata flags (#27074) --- .../templates/release-upload-buildinfo.yml | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml index c470af1fd6e..9e3d6a6accb 100644 --- a/.pipelines/templates/release-upload-buildinfo.yml +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -51,14 +51,17 @@ jobs: Import-Module "$toolsDirectory/ci.psm1" $jsonFile = Get-Item "$ENV:PIPELINE_WORKSPACE/PSPackagesOfficial/BuildInfoJson/*.json" $fileName = Split-Path $jsonFile -Leaf + # The build itself has already determined if it is preview or stable/LTS, + # we just need to check via the file name + $isPreview = $fileName -eq "preview.json" + $isStable = $fileName -eq "stable.json" $dateTime = [datetime]::UtcNow $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) $metadata = Get-Content -LiteralPath "$toolsDirectory/metadata.json" -ErrorAction Stop | ConvertFrom-Json - $stableReleaseTag = $metadata.StableReleaseTag -Replace 'v','' - - $currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v','' + # Note: version tags in metadata.json (e.g. StableReleaseTag) may not reflect the current release being + # published, so they must not be used to gate channel decisions. Use the explicit publish flags instead. $stableRelease = $metadata.StableRelease.PublishToChannels $ltsRelease = $metadata.LTSRelease.PublishToChannels @@ -73,7 +76,7 @@ jobs: $targetFile = "$ENV:PIPELINE_WORKSPACE/$fileName" ConvertTo-Json -InputObject $buildInfo | Out-File $targetFile -Encoding ascii - if ($fileName -eq "preview.json") { + if ($isPreview) { Set-BuildVariable -Name UploadPreview -Value YES } else { Set-BuildVariable -Name UploadPreview -Value NO @@ -82,9 +85,7 @@ jobs: Set-BuildVariable -Name PreviewBuildInfoFile -Value $targetFile ## Create 'lts.json' if marked as a LTS release. - if ($fileName -eq "stable.json") { - [System.Management.Automation.SemanticVersion] $stableVersion = $stableReleaseTag - [System.Management.Automation.SemanticVersion] $currentVersion = $currentReleaseTag + if ($isStable) { if ($ltsRelease) { $ltsFile = "$ENV:PIPELINE_WORKSPACE/lts.json" Copy-Item -Path $targetFile -Destination $ltsFile -Force @@ -94,18 +95,24 @@ jobs: Set-BuildVariable -Name UploadLTS -Value NO } - ## Only update the stable.json if the current version is greater than the stable version. - if ($currentVersion -gt $stableVersion) { - $versionFile = "$ENV:PIPELINE_WORKSPACE/$($currentVersion.Major)-$($currentVersion.Minor).json" - Copy-Item -Path $targetFile -Destination $versionFile -Force - Set-BuildVariable -Name StableBuildInfoFile -Value $versionFile + ## Gate stable.json upload on the metadata publish flag. + if ($stableRelease) { + Set-BuildVariable -Name StableBuildInfoFile -Value $targetFile Set-BuildVariable -Name UploadStable -Value YES } else { Set-BuildVariable -Name UploadStable -Value NO } + ## Always publish the version-specific {Major}-{Minor}.json for non-preview builds. + [System.Management.Automation.SemanticVersion] $currentVersion = $currentReleaseTag + $versionFile = "$ENV:PIPELINE_WORKSPACE/$($currentVersion.Major)-$($currentVersion.Minor).json" + Copy-Item -Path $targetFile -Destination $versionFile -Force + Set-BuildVariable -Name VersionSpecificBuildInfoFile -Value $versionFile + Set-BuildVariable -Name UploadVersionSpecific -Value YES + } else { Set-BuildVariable -Name UploadStable -Value NO + Set-BuildVariable -Name UploadVersionSpecific -Value NO } displayName: Create json files @@ -146,4 +153,12 @@ jobs: Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - condition: and(succeeded(), or(eq(variables['UploadPreview'], 'YES'), eq(variables['UploadLTS'], 'YES'), eq(variables['UploadStable'], 'YES'))) + + #version-specific + if ($env:UploadVersionSpecific -eq 'YES') { + $jsonFile = "$env:VersionSpecificBuildInfoFile" + $blobName = Get-Item $jsonFile | Split-Path -Leaf + Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" + Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force + } + condition: and(succeeded(), or(eq(variables['UploadPreview'], 'YES'), eq(variables['UploadLTS'], 'YES'), eq(variables['UploadStable'], 'YES'), eq(variables['UploadVersionSpecific'], 'YES'))) From 1e5c02074d6d98a1b11851ddcc09332aca6c1aa1 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 20 Mar 2026 15:44:53 -0700 Subject: [PATCH 230/255] Update the `PhoneProductId` to be the official LTS id used by Store (#27077) --- tools/packaging/packaging.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index baab911f60d..d420ceb620d 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -4314,7 +4314,7 @@ function New-MSIXPackage Write-Verbose "Using Preview assets" -Verbose } elseif ($LTS) { # This is the PhoneProductId for the "Microsoft.PowerShell-LTS" package. - $PhoneProductId = "a9af273a-c636-47ac-bc2a-775edf80b2b9" + $PhoneProductId = "b7a4b003-3704-47a9-b018-cfcc9801f4fc" Write-Verbose "Using LTS assets" -Verbose } From c11d721165e823f74845db09e9d14a88d5b62373 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:27:35 -0400 Subject: [PATCH 231/255] Bump github/codeql-action from 4.32.6 to 4.34.1 (#27087) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 21966cb855c..dfe565b0319 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5 + uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5 + uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 7e868e10dbf..7bd7bf6314d 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v3.29.5 with: sarif_file: results.sarif From f9be17e6b5c9ac58c187cb29c43c1af65967e8c8 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:37:51 -0500 Subject: [PATCH 232/255] Separate Official and NonOfficial templates for ADO pipelines (#26897) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/agents/SplitADOPipelines.agent.md | 164 ++++++++ ...Shell-Coordinated_Packages-NonOfficial.yml | 97 +++++ .../PowerShell-Packages-NonOfficial.yml | 97 +++++ .../PowerShell-Release-Azure-NonOfficial.yml | 76 ++++ .../PowerShell-Release-NonOfficial.yml | 106 ++++++ .../PowerShell-vPack-NonOfficial.yml | 88 +++++ ...werShell-Coordinated_Packages-Official.yml | 247 +----------- .pipelines/PowerShell-Packages-Official.yml | 227 +---------- .../PowerShell-Release-Official-Azure.yml | 34 +- .pipelines/PowerShell-Release-Official.yml | 358 +----------------- .pipelines/PowerShell-vPack-Official.yml | 268 +------------ .pipelines/templates/release-MSIX-Publish.yml | 2 +- .pipelines/templates/release-githubNuget.yml | 4 +- ...PowerShell-Coordinated_Packages-Stages.yml | 202 ++++++++++ .../stages/PowerShell-Packages-Stages.yml | 186 +++++++++ .../stages/PowerShell-Release-Stages.yml | 323 ++++++++++++++++ .../stages/PowerShell-vPack-Stages.yml | 236 ++++++++++++ ...erShell-Coordinated_Packages-Variables.yml | 67 ++++ .../PowerShell-Packages-Variables.yml | 50 +++ .../PowerShell-Release-Azure-Variables.yml | 35 ++ .../PowerShell-Release-Variables.yml | 41 ++ .../variables/PowerShell-vPack-Variables.yml | 39 ++ .../release-shared.yml | 0 23 files changed, 1858 insertions(+), 1089 deletions(-) create mode 100644 .github/agents/SplitADOPipelines.agent.md create mode 100644 .pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml create mode 100644 .pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml create mode 100644 .pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml create mode 100644 .pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml create mode 100644 .pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml create mode 100644 .pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml create mode 100644 .pipelines/templates/stages/PowerShell-Packages-Stages.yml create mode 100644 .pipelines/templates/stages/PowerShell-Release-Stages.yml create mode 100644 .pipelines/templates/stages/PowerShell-vPack-Stages.yml create mode 100644 .pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml create mode 100644 .pipelines/templates/variables/PowerShell-Packages-Variables.yml create mode 100644 .pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml create mode 100644 .pipelines/templates/variables/PowerShell-Release-Variables.yml create mode 100644 .pipelines/templates/variables/PowerShell-vPack-Variables.yml rename .pipelines/templates/{variable => variables}/release-shared.yml (100%) diff --git a/.github/agents/SplitADOPipelines.agent.md b/.github/agents/SplitADOPipelines.agent.md new file mode 100644 index 00000000000..8322f473e7b --- /dev/null +++ b/.github/agents/SplitADOPipelines.agent.md @@ -0,0 +1,164 @@ +--- +name: SplitADOPipelines +description: This agent will implement and restructure the repository's existing ADO pipelines into Official and NonOfficial pipelines. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'todo'] +--- + +This agent will implement and restructure the repository's existing ADO pipelines into Official and NonOfficial pipelines. + +A repository will have under the ./pipelines directory a series of yaml files that define the ADO pipelines for the repository. + +First confirm if the pipelines are using a toggle switch for Official and NonOfficial. This will look something like this + +```yaml +parameters: + - name: templateFile + value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} +``` + +Followed by: + +```yaml +extends: + template: ${{ variables.templateFile }} +``` + +This is an indicator that this work needs to be done. This toggle switch is no longer allowed and the templates need to be hard coded. + +## Refactoring Steps + +### Step 1: Extract Shared Templates + +For each pipeline file that uses the toggle switch pattern (e.g., `PowerShell-Packages.yml`): + +1. Create a `./pipelines/templates` directory if it doesn't exist +2. Extract the **variables section** into `./pipelines/templates/PowerShell-Packages-Variables.yml` +3. Extract the **stages section** into `./pipelines/templates/PowerShell-Packages-Stages.yml` + +**IMPORTANT**: Only extract the `variables:` and `stages:` sections. All other sections (parameters, resources, extends, etc.) remain in the pipeline files. + +### Step 2: Create Official Pipeline (In-Place Refactoring) + +The original toggle-based file becomes the Official pipeline: + +1. **Keep the file in its original location** (e.g., `./pipelines/PowerShell-Packages.yml` stays where it is) +2. Remove the toggle switch parameter (`templateFile` parameter) +3. Hard-code the Official template reference: + ```yaml + extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + ``` +4. Replace the `variables:` section with a template reference: + ```yaml + variables: + - template: templates/PowerShell-Packages-Variables.yml + ``` +5. Replace the `stages:` section with a template reference: + ```yaml + stages: + - template: templates/PowerShell-Packages-Stages.yml + ``` + +### Step 3: Create NonOfficial Pipeline + +1. Create `./pipelines/NonOfficial` directory if it doesn't exist +2. Create the NonOfficial pipeline file (e.g., `./pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml`) +3. Copy the structure from the refactored Official pipeline +4. Hard-code the NonOfficial template reference: + ```yaml + extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + ``` +5. Reference the same shared templates: + ```yaml + variables: + - template: ../templates/PowerShell-Packages-Variables.yml + + stages: + - template: ../templates/PowerShell-Packages-Stages.yml + ``` + +**Note**: The NonOfficial pipeline uses `../templates/` because it's one directory deeper than the Official pipeline. + +### Step 4: Link NonOfficial Pipelines to NonOfficial Dependencies + +After creating NonOfficial pipelines, ensure they consume artifacts from other **NonOfficial** pipelines, not Official ones. + +1. **Check the `resources:` section** in each NonOfficial pipeline for `pipelines:` dependencies +2. **Identify Official pipeline references** that need to be changed to NonOfficial +3. **Update the `source:` field** to point to the NonOfficial version + +**Example Problem:** NonOfficial pipeline pointing to Official dependency +```yaml +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' # ❌ Wrong - Official! +``` + +**Solution:** Update to NonOfficial dependency +```yaml +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-NonOfficial' # ✅ Correct - NonOfficial! +``` + +**IMPORTANT**: The `source:` field must match the **exact ADO pipeline definition name** as it appears in Azure DevOps, not necessarily the file name. + +### Step 5: Configure Release Environment Parameters (NonAzure Only) + +**This step only applies if the pipeline uses `category: NonAzure` in the release configuration.** + +If you detect this pattern in the original pipeline: + +```yaml +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates # or NonOfficial + parameters: + release: + category: NonAzure +``` + +Then you must configure the `ob_release_environment` parameter when referencing the stages template. + +#### Official Pipeline Configuration + +In the Official pipeline (e.g., `./pipelines/PowerShell-Packages.yml`): + +```yaml +stages: + - template: templates/PowerShell-Packages-Stages.yml + parameters: + ob_release_environment: Production +``` + +#### NonOfficial Pipeline Configuration + +In the NonOfficial pipeline (e.g., `./pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml`): + +```yaml +stages: + - template: ../templates/PowerShell-Packages-Stages.yml + parameters: + ob_release_environment: Test +``` + +#### Update Stages Template to Accept Parameter + +The extracted stages template (e.g., `./pipelines/templates/PowerShell-Packages-Stages.yml`) must declare the parameter at the top: + +```yaml +parameters: + - name: ob_release_environment + type: string + +stages: + # ... rest of stages configuration using ${{ parameters.ob_release_environment }} +``` + +**IMPORTANT**: +- Only configure this for pipelines with `category: NonAzure` +- Official pipelines always use `ob_release_environment: Production` +- NonOfficial pipelines always use `ob_release_environment: Test` +- The stages template must accept this parameter and use it in the appropriate stage configurations diff --git a/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml new file mode 100644 index 00000000000..55d4c4557d8 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml @@ -0,0 +1,97 @@ +trigger: none + +parameters: + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Debugging - Skip Signing + type: string + default: 'NO' + - name: RUN_TEST_AND_RELEASE + displayName: Debugging - Run Test and Release Artifacts Stage + type: boolean + default: true + - name: RUN_WINDOWS + displayName: Debugging - Enable Windows Stage + type: boolean + default: true + - name: ENABLE_MSBUILD_BINLOGS + displayName: Debugging - Enable MSBuild Binary Logs + type: boolean + default: false + - name: FORCE_CODEQL + displayName: Debugging - Enable CodeQL and set cadence to 1 hour + type: boolean + default: false + +name: bins-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + ref: master + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +variables: + - template: ../templates/variables/PowerShell-Coordinated_Packages-Variables.yml + parameters: + InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + SKIP_SIGNING: ${{ parameters.SKIP_SIGNING }} + ENABLE_MSBUILD_BINLOGS: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + FORCE_CODEQL: ${{ parameters.FORCE_CODEQL }} + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + customTags: 'ES365AIMigrationTooling' + featureFlags: + LinuxHostVersion: + Network: KS3 + WindowsHostVersion: + Network: KS3 + incrementalSDLBinaryAnalysis: true + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + codeql: + compiled: + enabled: $(CODEQL_ENABLED) + tsaEnabled: true # This enables TSA bug filing only for CodeQL 3000 + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: ../templates/stages/PowerShell-Coordinated_Packages-Stages.yml + parameters: + RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} + RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} + OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml new file mode 100644 index 00000000000..81f343a04a0 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml @@ -0,0 +1,97 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: ForceAzureBlobDelete + displayName: Delete Azure Blob + type: string + values: + - true + - false + default: false + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: disableNetworkIsolation + type: boolean + default: false + +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: ../templates/variables/PowerShell-Packages-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} + +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + cloudvault: + enabled: false + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + LinuxHostVersion: + Network: KS3 + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + disableNetworkIsolation: ${{ variables.disableNetworkIsolation }} + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + stages: + - template: ../templates/stages/PowerShell-Packages-Stages.yml + parameters: + OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml new file mode 100644 index 00000000000..681babb2220 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml @@ -0,0 +1,76 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: skipPublish + displayName: Skip PMC Publish + type: boolean + default: false + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + +name: ev2-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: ../templates/variables/PowerShell-Release-Azure-Variables.yml + parameters: + debug: ${{ parameters.debug }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: Netlock + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + tsaOptionsFile: .config\tsaoptions.json + stages: + - template: /.pipelines/templates/release-prep-for-ev2.yml@self + parameters: + skipPublish: ${{ parameters.skipPublish }} + + - template: /.pipelines/templates/release-publish-pmc.yml@self diff --git a/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml new file mode 100644 index 00000000000..ca5a6383f33 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml @@ -0,0 +1,106 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: SkipPublish + displayName: Skip Publishing to Nuget + type: boolean + default: false + - name: SkipPSInfraInstallers + displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location + type: boolean + default: false + - name: skipMSIXPublish + displayName: Skip MSIX Publish + type: boolean + default: false + +name: release-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: ../templates/variables/PowerShell-Release-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + - repository: PSInternalTools + type: git + name: PowerShellCore/Internal-PowerShellTeam-Tools + ref: refs/heads/master + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + + # NOTE: The alias name "PSPackagesOfficial" is intentionally reused here even + # for the NonOfficial pipeline source. Downstream shared templates (for example, + # release-validate-sdk.yml and release-upload-buildinfo.yml) reference artifacts + # using `download: PSPackagesOfficial`, so changing this alias would break them. + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + release: + category: NonAzure + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + # suppression: + # suppressionFile: $(Build.SourcesDirectory)\.gdn\global.gdnsuppress + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: ../templates/stages/PowerShell-Release-Stages.yml + parameters: + releaseEnvironment: Test + SkipPublish: ${{ parameters.SkipPublish }} + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} diff --git a/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml new file mode 100644 index 00000000000..642b169adaf --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml @@ -0,0 +1,88 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time +- name: 'createVPack' + displayName: 'Create and Submit VPack' + type: boolean + default: true +- name: vPackName + type: string + displayName: 'VPack Name:' + default: 'PowerShell.BuildTool' + values: + - PowerShell.BuildTool + - PowerShell + - PowerShellDoNotUse +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" + +name: vPack_$(Build.SourceBranchName)_NonOfficial_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) + +variables: + - template: ../templates/variables/PowerShell-vPack-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + netiso: ${{ parameters.netiso }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/Microsoft.NonOfficial.yml@onebranchTemplates + parameters: + platform: + name: 'windows_undocked' # windows undocked + + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + + cloudvault: + enabled: false + + globalSdl: + useCustomPolicy: true # for signing code + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + stages: + - template: ../templates/stages/PowerShell-vPack-Stages.yml + parameters: + createVPack: ${{ parameters.createVPack }} + vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml index 12460e2861c..82f129a0a5e 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -30,7 +30,7 @@ parameters: type: boolean default: false -name: bins-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) +name: bins-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) resources: repositories: @@ -45,57 +45,13 @@ resources: ref: refs/heads/main variables: - - name: PS_RELEASE_BUILD - value: 1 - - name: DOTNET_CLI_TELEMETRY_OPTOUT - value: 1 - - name: POWERSHELL_TELEMETRY_OPTOUT - value: 1 - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: branchCounterKey - value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] - - name: branchCounter - value: $[counter(variables['branchCounterKey'], 1)] - - name: BUILDSECMON_OPT_IN - value: true - - name: __DOTNET_RUNTIME_FEED - value: ${{ parameters.InternalSDKBlobURL }} - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - - name: WindowsContainerImage - value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - name: SKIP_SIGNING - value: ${{ parameters.SKIP_SIGNING }} - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - name: ENABLE_MSBUILD_BINLOGS - value: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} - - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: - # Cadence is hours before CodeQL will allow a re-upload of the database - - name: CodeQL.Cadence - value: 1 - - name: CODEQL_ENABLED - ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: - value: true - ${{ else }}: - value: false - # Fix for BinSkim ICU package error in Linux containers - - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT - value: true - # Disable BinSkim at job level to override NonOfficial template defaults - - name: ob_sdl_binskim_enabled - value: false - - name: ps_official_build - value: true + - template: templates/variables/PowerShell-Coordinated_Packages-Variables.yml + parameters: + InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + SKIP_SIGNING: ${{ parameters.SKIP_SIGNING }} + ENABLE_MSBUILD_BINLOGS: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + FORCE_CODEQL: ${{ parameters.FORCE_CODEQL }} extends: template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates @@ -135,185 +91,8 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - stage: prep - jobs: - - job: SetVars - displayName: Set Variables - pool: - type: linux - - variables: - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' - - name: ob_sdl_codeSignValidation_enabled - value: false - - name: ob_sdl_codeql_compiled_enabled - value: false - - name: ob_sdl_credscan_suppressionsFile - value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - - name: ob_sdl_tsa_configFile - value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - - name: ob_signing_setup_enabled - value: false - - name: ob_sdl_sbom_enabled - value: false - - steps: - - checkout: self - clean: true - env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - - - pwsh: | - Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment variables - env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - - - template: /.pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - - - stage: macos - displayName: macOS - build and sign - dependsOn: ['prep'] - jobs: - - template: /.pipelines/templates/mac.yml@self - parameters: - buildArchitecture: x64 - - template: /.pipelines/templates/mac.yml@self - parameters: - buildArchitecture: arm64 - - - stage: linux - displayName: linux - build and sign - dependsOn: ['prep'] - jobs: - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-x64' - JobName: 'linux_x64' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-x64' - JobName: 'linux_x64_minSize' - BuildConfiguration: 'minSize' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-arm' - JobName: 'linux_arm' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-arm64' - JobName: 'linux_arm64' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent-linux-x64' - JobName: 'linux_fxd_x64_mariner' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent-linux-arm64' - JobName: 'linux_fxd_arm64_mariner' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent-noopt-linux-musl-x64' - JobName: 'linux_fxd_x64_alpine' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent' - JobName: 'linux_fxd' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-musl-x64' - JobName: 'linux_x64_alpine' - - - stage: windows - displayName: windows - build and sign - dependsOn: ['prep'] - condition: and(succeeded(),eq('${{ parameters.RUN_WINDOWS }}','true')) - jobs: - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: x64 - BuildConfiguration: release - JobName: build_windows_x64_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: x64 - BuildConfiguration: minSize - JobName: build_windows_x64_minSize_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: x86 - JobName: build_windows_x86_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: arm64 - JobName: build_windows_arm64_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: fxdependent - JobName: build_windows_fxdependent_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: fxdependentWinDesktop - JobName: build_windows_fxdependentWinDesktop_release - - - stage: test_and_release_artifacts - displayName: Test and Release Artifacts - dependsOn: ['prep'] - condition: and(succeeded(),eq('${{ parameters.RUN_TEST_AND_RELEASE }}','true')) - jobs: - - template: /.pipelines/templates/testartifacts.yml@self - - - job: release_json - displayName: Create and Upload release.json - pool: - type: windows - variables: - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: ob_sdl_tsa_configFile - value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - - name: ob_sdl_credscan_suppressionsFile - value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - steps: - - checkout: self - clean: true - - template: /.pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - template: /.pipelines/templates/rebuild-branch-check.yml@self - - powershell: | - $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json - - # Use the rebuild branch check from the template - $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' - - # Don't mark as LTS release for rebuild branches - $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch - - if ($isRebuildBranch) { - Write-Verbose -Message "Rebuild branch detected, not marking as LTS release" -Verbose - } - - @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" - Get-Content "$(Build.StagingDirectory)\release.json" - - if (-not (Test-Path "$(ob_outputDirectory)\metadata")) { - New-Item -ItemType Directory -Path "$(ob_outputDirectory)\metadata" - } - - Copy-Item -Path "$(Build.StagingDirectory)\release.json" -Destination "$(ob_outputDirectory)\metadata" -Force - displayName: Create and upload release.json file to build artifact - retryCountOnTaskFailure: 2 - - template: /.pipelines/templates/step/finalize.yml@self + - template: templates/stages/PowerShell-Coordinated_Packages-Stages.yml + parameters: + RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} + RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} + OfficialBuild: true diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml index a13ef12378a..8afce29ede7 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -28,44 +28,15 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: boolean default: false -name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] # needed for onebranch.pipeline.version task - - name: system.debug - value: ${{ parameters.debug }} - - name: ENABLE_PRS_DELAYSIGN - value: 1 - - name: ROOT - value: $(Build.SourcesDirectory) - - name: ForceAzureBlobDelete - value: ${{ parameters.ForceAzureBlobDelete }} - - name: NUGET_XMLDOC_MODE - value: none - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - name: branchCounterKey - value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] - - name: branchCounter - value: $[counter(variables['branchCounterKey'], 1)] - - group: MSIXSigningProfile - - name: disableNetworkIsolation - value: ${{ parameters.disableNetworkIsolation }} + - template: templates/variables/PowerShell-Packages-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} resources: pipelines: @@ -121,184 +92,6 @@ extends: enabled: false tsaOptionsFile: .config\tsaoptions.json stages: - - stage: prep - displayName: 'Prep BuildInfo+Az' - jobs: - - template: /.pipelines/templates/checkAzureContainer.yml@self - - - stage: mac_package - displayName: 'macOS Pkg+Sign' - dependsOn: [] - jobs: - - template: /.pipelines/templates/mac-package-build.yml@self - parameters: - buildArchitecture: x64 - - - template: /.pipelines/templates/mac-package-build.yml@self - parameters: - buildArchitecture: arm64 - - - stage: windows_package_build - displayName: 'Win Pkg (unsigned)' - dependsOn: [] - jobs: - - template: /.pipelines/templates/packaging/windows/package.yml@self - parameters: - runtime: x64 - - - template: /.pipelines/templates/packaging/windows/package.yml@self - parameters: - runtime: arm64 - - - template: /.pipelines/templates/packaging/windows/package.yml@self - parameters: - runtime: x86 - - - template: /.pipelines/templates/packaging/windows/package.yml@self - parameters: - runtime: fxdependent - - - template: /.pipelines/templates/packaging/windows/package.yml@self - parameters: - runtime: fxdependentWinDesktop - - - template: /.pipelines/templates/packaging/windows/package.yml@self - parameters: - runtime: minsize - - - stage: windows_package_sign - displayName: 'Win Pkg Sign' - dependsOn: [windows_package_build] - jobs: - - template: /.pipelines/templates/packaging/windows/sign.yml@self - parameters: - runtime: x64 - - - template: /.pipelines/templates/packaging/windows/sign.yml@self - parameters: - runtime: arm64 - - - template: /.pipelines/templates/packaging/windows/sign.yml@self - parameters: - runtime: x86 - - - template: /.pipelines/templates/packaging/windows/sign.yml@self - parameters: - runtime: fxdependent - - - template: /.pipelines/templates/packaging/windows/sign.yml@self - parameters: - runtime: fxdependentWinDesktop - - - template: /.pipelines/templates/packaging/windows/sign.yml@self - parameters: - runtime: minsize - - - stage: linux_package - displayName: 'Linux Pkg+Sign' - dependsOn: [] - jobs: - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64' - signedDrop: 'drop_linux_sign_linux_x64' - packageType: deb - jobName: deb - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd_x64_mariner' - signedDrop: 'drop_linux_sign_linux_fxd_x64_mariner' - packageType: rpm-fxdependent #mariner-x64 - jobName: mariner_x64 - signingProfile: 'CP-459159-pgpdetached' - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd_arm64_mariner' - signedDrop: 'drop_linux_sign_linux_fxd_arm64_mariner' - packageType: rpm-fxdependent-arm64 #mariner-arm64 - jobName: mariner_arm64 - signingProfile: 'CP-459159-pgpdetached' - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64' - signedDrop: 'drop_linux_sign_linux_x64' - packageType: rpm - jobName: rpm - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_arm' - signedDrop: 'drop_linux_sign_linux_arm' - packageType: tar-arm - jobName: tar_arm - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_arm64' - signedDrop: 'drop_linux_sign_linux_arm64' - packageType: tar-arm64 - jobName: tar_arm64 - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64_alpine' - signedDrop: 'drop_linux_sign_linux_x64_alpine' - packageType: tar-alpine - jobName: tar_alpine - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd' - signedDrop: 'drop_linux_sign_linux_fxd' - packageType: fxdependent - jobName: fxdependent - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64' - signedDrop: 'drop_linux_sign_linux_x64' - packageType: tar - jobName: tar - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd_x64_alpine' - signedDrop: 'drop_linux_sign_linux_fxd_x64_alpine' - packageType: tar-alpine-fxdependent - jobName: tar_alpine_fxd - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64_minSize' - signedDrop: 'drop_linux_sign_linux_x64_minSize' - packageType: min-size - jobName: minSize - - - stage: nupkg - displayName: 'NuGet Pkg+Sign' - dependsOn: [] - jobs: - - template: /.pipelines/templates/nupkg.yml@self - - - stage: msixbundle - displayName: 'MSIX Bundle+Sign' - dependsOn: [windows_package_build] # Only depends on unsigned packages - jobs: - - template: /.pipelines/templates/package-create-msix.yml@self - parameters: - OfficialBuild: true - - - stage: upload - displayName: 'Upload' - dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON - jobs: - - template: /.pipelines/templates/uploadToAzure.yml@self - - - stage: validatePackages - displayName: 'Validate Packages' - dependsOn: [upload] - jobs: - - template: /.pipelines/templates/release-validate-packagenames.yml@self + - template: templates/stages/PowerShell-Packages-Stages.yml + parameters: + OfficialBuild: true diff --git a/.pipelines/PowerShell-Release-Official-Azure.yml b/.pipelines/PowerShell-Release-Official-Azure.yml index 81543420460..24040a2463d 100644 --- a/.pipelines/PowerShell-Release-Official-Azure.yml +++ b/.pipelines/PowerShell-Release-Official-Azure.yml @@ -14,38 +14,12 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: string default: 'NO' -name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) +name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: system.debug - value: ${{ parameters.debug }} - - name: ENABLE_PRS_DELAYSIGN - value: 1 - - name: ROOT - value: $(Build.SourcesDirectory) - - name: REPOROOT - value: $(Build.SourcesDirectory) - - name: OUTPUTROOT - value: $(REPOROOT)\out - - name: NUGET_XMLDOC_MODE - value: none - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: ob_sdl_tsa_configFile - value: $(Build.SourcesDirectory)\.config\tsaoptions.json - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - - group: PoolNames + - template: templates/variables/PowerShell-Release-Azure-Variables.yml + parameters: + debug: ${{ parameters.debug }} resources: repositories: diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml index fa14b9b0acb..3528e6b1471 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -30,43 +30,13 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: boolean default: false -name: release-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId) +name: release-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: system.debug - value: ${{ parameters.debug }} - - name: ENABLE_PRS_DELAYSIGN - value: 1 - - name: ROOT - value: $(Build.SourcesDirectory) - - name: REPOROOT - value: $(Build.SourcesDirectory) - - name: OUTPUTROOT - value: $(REPOROOT)\out - - name: NUGET_XMLDOC_MODE - value: none - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - group: PoolNames - - name: releaseEnvironment - value: 'Production' - # Fix for BinSkim ICU package error in Linux containers - - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT - value: true + - template: templates/variables/PowerShell-Release-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} resources: repositories: @@ -124,315 +94,9 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - stage: setReleaseTagAndChangelog - displayName: 'Set Release Tag and Upload Changelog' - jobs: - - template: /.pipelines/templates/release-SetTagAndChangelog.yml@self - - - stage: validateSdk - displayName: 'Validate SDK' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-sdk.yml@self - parameters: - jobName: "windowsSDK" - displayName: "Windows SDK Validation" - imageName: PSMMS2019-Secure - poolName: $(windowsPool) - - - template: /.pipelines/templates/release-validate-sdk.yml@self - parameters: - jobName: "MacOSSDK" - displayName: "MacOS SDK Validation" - imageName: macOS-latest - poolName: Azure Pipelines - - - template: /.pipelines/templates/release-validate-sdk.yml@self - parameters: - jobName: "LinuxSDK" - displayName: "Linux SDK Validation" - imageName: PSMMSUbuntu22.04-Secure - poolName: $(ubuntuPool) - - - stage: gbltool - displayName: 'Validate Global tools' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-globaltools.yml@self - parameters: - jobName: "WindowsGlobalTools" - displayName: "Windows Global Tools Validation" - jobtype: windows - - - template: /.pipelines/templates/release-validate-globaltools.yml@self - parameters: - jobName: "LinuxGlobalTools" - displayName: "Linux Global Tools Validation" - jobtype: linux - globalToolExeName: 'pwsh' - globalToolPackageName: 'PowerShell.Linux.x64' - - - stage: fxdpackages - displayName: 'Validate FXD Packages' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'winfxd' - displayName: 'Validate Win Fxd Packages' - jobtype: 'windows' - artifactName: 'drop_windows_package_package_win_fxdependent' - packageNamePattern: '**/*win-fxdependent.zip' - - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'winfxdDesktop' - displayName: 'Validate WinDesktop Fxd Packages' - jobtype: 'windows' - artifactName: 'drop_windows_package_package_win_fxdependentWinDesktop' - packageNamePattern: '**/*win-fxdependentwinDesktop.zip' - - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'linuxfxd' - displayName: 'Validate Linux Fxd Packages' - jobtype: 'linux' - artifactName: 'drop_linux_package_fxdependent' - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'linuxArm64fxd' - displayName: 'Validate Linux ARM64 Fxd Packages' - jobtype: 'linux' - artifactName: 'drop_linux_package_fxdependent' - # this is really an architecture independent package - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - arm64: 'yes' - enableCredScan: false - - - stage: ManualValidation - dependsOn: [] - displayName: Manual Validation - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Validate Windows Packages - jobName: ValidateWinPkg - instructions: | - Validate zip package on windows - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Validate OSX Packages - jobName: ValidateOsxPkg - instructions: | - Validate tar.gz package on osx-arm64 - - - stage: ReleaseAutomation - dependsOn: [] - displayName: 'Release Automation' - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start Release Automation - jobName: StartRA - instructions: | - Kick off Release automation build at: https://dev.azure.com/powershell-rel/Release-Automation/_build?definitionId=10&_a=summary - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Triage results - jobName: TriageRA - dependsOnJob: StartRA - instructions: | - Triage ReleaseAutomation results - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Signoff Tests - dependsOnJob: TriageRA - jobName: SignoffTests - instructions: | - Signoff ReleaseAutomation results - - - stage: UpdateChangeLog - displayName: Update the changelog - dependsOn: - - ManualValidation - - ReleaseAutomation - - fxdpackages - - gbltool - - validateSdk - - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Make sure the changelog is updated - jobName: MergeChangeLog - instructions: | - Update and merge the changelog for the release. - This step is required for creating GitHub draft release. - - - stage: PublishGitHubReleaseAndNuget - displayName: Publish GitHub and Nuget Release - dependsOn: - - setReleaseTagAndChangelog - - UpdateChangeLog - variables: - ob_release_environment: ${{ variables.releaseEnvironment }} - jobs: - - template: /.pipelines/templates/release-githubNuget.yml@self - parameters: - skipPublish: ${{ parameters.SkipPublish }} - - - stage: PushGitTagAndMakeDraftPublic - displayName: Push Git Tag and Make Draft Public - dependsOn: PublishGitHubReleaseAndNuget - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Push Git Tag - jobName: PushGitTag - instructions: | - Push the git tag to upstream - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Make Draft Public - dependsOnJob: PushGitTag - jobName: DraftPublic - instructions: | - Make the GitHub Release Draft Public - - - stage: BlobPublic - displayName: Make Blob Public - dependsOn: - - UpdateChangeLog - - PushGitTagAndMakeDraftPublic - jobs: - - template: /.pipelines/templates/release-MakeBlobPublic.yml@self - parameters: - SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} - - - stage: PublishPMC - displayName: Publish PMC - dependsOn: PushGitTagAndMakeDraftPublic - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Publish to PMC - jobName: ReleaseToPMC - instructions: | - Run PowerShell-Release-Official-Azure.yml pipeline to publish to PMC - - - stage: UpdateDotnetDocker - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Update DotNet SDK Docker images - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Update .NET SDK docker images - jobName: DotnetDocker - instructions: | - Create PR for updating dotnet-docker images to use latest PowerShell version. - 1. Fork and clone https://github.com/dotnet/dotnet-docker.git - 2. git checkout upstream/nightly -b updatePS - 3. dotnet run --project .\eng\update-dependencies\ specific --product-version powershell= --compute-shas - 4. create PR targeting nightly branch - - - stage: UpdateWinGet - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Add manifest entry to winget - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Add manifest entry to winget - jobName: UpdateWinGet - instructions: | - This is typically done by the community 1-2 days after the release. - - - stage: PublishMsix - dependsOn: - - setReleaseTagAndChangelog - - PushGitTagAndMakeDraftPublic - displayName: Publish MSIX to store - variables: - ob_release_environment: ${{ variables.releaseEnvironment }} - jobs: - - template: /.pipelines/templates/release-MSIX-Publish.yml@self - parameters: - skipMSIXPublish: ${{ parameters.skipMSIXPublish }} - - - stage: PublishVPack - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Release vPack - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start 2 vPack Release pipelines - jobName: PublishVPack - instructions: | - 1. Kick off PowerShell-vPack-Official pipeline - 2. Kick off PowerShell-MSIXBundle-VPack pipeline - - # Need to verify if the Az PS / CLI team still uses this. Skippinng for this release. - # - stage: ReleaseDeps - # dependsOn: GitHubTasks - # displayName: Update pwsh.deps.json links - # jobs: - # - template: templates/release-UpdateDepsJson.yml - - - stage: UploadBuildInfoJson - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Upload BuildInfo.json - jobs: - - template: /.pipelines/templates/release-upload-buildinfo.yml@self - - - stage: ReleaseSymbols - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Release Symbols - jobs: - - template: /.pipelines/templates/release-symbols.yml@self - - - stage: ChangesToMaster - displayName: Ensure changes are in GH master - dependsOn: - - PublishPMC - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Make sure changes are in master - jobName: MergeToMaster - instructions: | - Make sure that changes README.md and metadata.json are merged into master on GitHub. - - - stage: ReleaseToMU - displayName: Release to MU - dependsOn: PushGitTagAndMakeDraftPublic # This only needs the blob to be available - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Release to MU - instructions: | - Notify the PM team to start the process of releasing to MU. - - - stage: ReleaseClose - displayName: Finish Release - dependsOn: - - ReleaseToMU - - ReleaseSymbols - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Retain Build - jobName: RetainBuild - instructions: | - Retain the build - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Delete release branch - jobName: DeleteBranch - instructions: | - Delete release + - template: templates/stages/PowerShell-Release-Stages.yml + parameters: + releaseEnvironment: Production + SkipPublish: ${{ parameters.SkipPublish }} + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index c49560bf2b8..13087fbbf65 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -30,37 +30,14 @@ parameters: # parameters are shown up in ADO UI in a build queue time - Netlock default: "R1" -name: vPack_$(Build.SourceBranchName)_Prod.true_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) +name: vPack_$(Build.SourceBranchName)_Prod_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: system.debug - value: ${{ parameters.debug }} - - name: BuildSolution - value: $(Build.SourcesDirectory)\dirs.proj - - name: BuildConfiguration - value: Release - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - - name: Codeql.Enabled - value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack - - name: DOTNET_CLI_TELEMETRY_OPTOUT - value: 1 - - name: POWERSHELL_TELEMETRY_OPTOUT - value: 1 - - name: nugetMultiFeedWarnLevel - value: none - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - group: Azure Blob variable group - - group: certificate_logical_to_actual # used within signing task - - group: DotNetPrivateBuildAccess - - group: certificate_logical_to_actual - - name: netiso - value: ${{ parameters.netiso }} -# We shouldn't be using PATs anymore -# - group: mscodehub-feed-read-general + - template: templates/variables/PowerShell-vPack-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + netiso: ${{ parameters.netiso }} resources: repositories: @@ -105,232 +82,7 @@ extends: enabled: false tsaOptionsFile: .config/tsaoptions.json stages: - - stage: BuildStage - jobs: - - job: BuildJob - pool: - type: windows - - strategy: - matrix: - x86: - architecture: x86 - - x64: - architecture: x64 - - arm64: - architecture: arm64 - - variables: - ArtifactPlatform: 'windows' - ob_artifactBaseName: drop_build_$(architecture) - ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' - ob_createvpack_enabled: ${{ parameters.createVPack }} - ob_createvpack_owneralias: tplunk - ob_createvpack_versionAs: parts - ob_createvpack_propsFile: true - ob_createvpack_verbose: true - ob_createvpack_packagename: '${{ parameters.vPackName }}.$(architecture)' - ob_createvpack_description: PowerShell $(architecture) $(version) - # I think the variables reload after we transition back to the host so this works. 🤷‍♂️ - ob_createvpack_majorVer: $(pwshMajorVersion) - ob_createvpack_minorVer: $(pwshMinorVersion) - ob_createvpack_patchVer: $(pwshPatchVersion) - ${{ if ne(variables['pwshPrereleaseVersion'], '') }}: - ob_createvpack_prereleaseVer: $(pwshPrereleaseVersion) - ${{ else }}: - ob_createvpack_prereleaseVer: $(Build.SourceVersion) - - steps: - - checkout: self - displayName: Checkout source code - during restore - clean: true - path: s - env: - ob_restore_phase: true - - - template: .pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - - - pwsh: | - $version = '$(Version)' - Write-Verbose -Verbose "Version: $version" - if(!$version) { - throw "Version is not set." - } - - $mainVersionParts = $version -split '-' - - Write-Verbose -Verbose "mainVersionParts: $($mainVersionParts[0]) ; $($mainVersionParts[1])" - $versionParts = $mainVersionParts[0] -split '[.]'; - $major = $versionParts[0] - $minor = $versionParts[1] - $patch = $versionParts[2] - - $previewPart = $mainVersionParts[1] - Write-Verbose -Verbose "previewPart: $previewPart" - - Write-Host "major: $major; minor: $minor; patch: $patch;" - - $vstsCommandString = "vso[task.setvariable variable=pwshMajorVersion]$major" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - - $vstsCommandString = "vso[task.setvariable variable=pwshMinorVersion]$minor" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - - $vstsCommandString = "vso[task.setvariable variable=pwshPatchVersion]$patch" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - if($previewPart) { - $vstsCommandString = "vso[task.setvariable variable=pwshPrereleaseVersion]$previewPart" - } else { - Write-Verbose -Verbose "No prerelease part found in version string." - } - displayName: Set ob_createvpack_*Ver - env: - ob_restore_phase: true - - # Validate pwsh*Version variables - - pwsh: | - $variables = @("pwshMajorVersion", "pwshMinorVersion", "pwshPatchVersion") - foreach ($var in $variables) { - if (-not (get-item "Env:\$var" -ErrorAction SilentlyContinue).value) { - throw "Required variable '`$env:$var' is not set." - } - } - displayName: Validate pwsh*Version variables - env: - ob_restore_phase: true - - - pwsh: | - if($env:RELEASETAGVAR -match '-') { - throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" - } - displayName: Stop any preview release - env: - ob_restore_phase: true - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - packageType: sdk - version: 3.1.x - installationPath: $(Agent.ToolsDirectory)/dotnet - - ### BUILD ### - - - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self - parameters: - repoRoot: $(repoRoot) - - - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. - env: - ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. - inputs: - Enabled: true - AnalyzeInPipeline: false # Do not upload results - Language: csharp - - - task: UseDotNet@2 - displayName: 'Install .NET based on global.json' - inputs: - useGlobalJson: true - workingDirectory: $(repoRoot) - env: - ob_restore_phase: true - - - pwsh: | - # Need to set PowerShellRoot variable for obp-file-signing template - $vstsCommandString = "vso[task.setvariable variable=PowerShellRoot]$(repoRoot)" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - - $Architecture = '$(Architecture)' - $runtime = switch ($Architecture) - { - "x64" { "win7-x64" } - "x86" { "win7-x86" } - "arm64" { "win-arm64" } - } - - $params = @{} - if ($env:BuildConfiguration -eq 'minSize') { - $params['ForMinimalSize'] = $true - } - - $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - - Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" - Import-Module -Name $(repoRoot)/build.psm1 -Force - $buildWithSymbolsPath = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/Symbols_$Architecture" -Force - - Start-PSBootstrap -Scenario Package - $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose - - $ReleaseTagParam = @{} - - if ($env:RELEASETAGVAR) { - $ReleaseTagParam['ReleaseTag'] = $env:RELEASETAGVAR - } - - Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore @params @ReleaseTagParam - - $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' - Write-Verbose -Verbose "refFolderPath: $refFolderPath" - $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' - $null = New-Item -ItemType Directory -Path $outputPath -Force - $psOptPath = "$outputPath/psoptions.json" - Save-PSOptions -PSOptionsPath $psOptPath - - Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" - displayName: Build Windows Universal - $(Architecture) -$(BuildConfiguration) Symbols folder - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. - - - task: CodeQL3000Finalize@0 # Add CodeQL Finalize task right after your 'Build' step. - env: - ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(repoRoot)\src' - ob_restore_phase: true - - - template: /.pipelines/templates/obp-file-signing.yml@self - parameters: - binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' - SigningProfile: $(windows_build_tools_cert_id) - OfficialBuild: false - vPackScenario: true - - ### END OF BUILD ### - - - pwsh: | - Get-ChildItem env:/ob_createvpack_*Ver - Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse - Get-Content "$(Pipeline.Workspace)\PowerShell\preview.json" -ErrorAction SilentlyContinue | Write-Host - displayName: Debug Output Directory and Version - condition: succeededOrFailed() - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - - pwsh: | - $vpackFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse - if($vpackFiles.Count -eq 0) { - throw "No files found in $(Pipeline.Workspace)\Symbols_$(Architecture)" - } - $vpackFiles - displayName: Debug Output Directory and Version - condition: succeededOrFailed() + - template: templates/stages/PowerShell-vPack-Stages.yml + parameters: + createVPack: ${{ parameters.createVPack }} + vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml index a92c71f826b..aaef3c6f269 100644 --- a/.pipelines/templates/release-MSIX-Publish.yml +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -21,7 +21,7 @@ jobs: value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsStable'] ] - name: PREVIEW value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsPreview'] ] - - template: ./variable/release-shared.yml@self + - template: ./variables/release-shared.yml@self parameters: RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] steps: diff --git a/.pipelines/templates/release-githubNuget.yml b/.pipelines/templates/release-githubNuget.yml index 206079c555f..95698554c40 100644 --- a/.pipelines/templates/release-githubNuget.yml +++ b/.pipelines/templates/release-githubNuget.yml @@ -17,7 +17,7 @@ jobs: pipeline: PSPackagesOfficial artifactName: drop_upload_upload_packages variables: - - template: ./variable/release-shared.yml@self + - template: ./variables/release-shared.yml@self parameters: RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] @@ -167,7 +167,7 @@ jobs: pipeline: PSPackagesOfficial artifactName: drop_upload_upload_packages variables: - - template: ./variable/release-shared.yml@self + - template: ./variables/release-shared.yml@self parameters: VERSION: $[ stageDependencies.setReleaseTagAndChangelog.SetTagAndChangelog.outputs['OutputVersion.Version'] ] diff --git a/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml new file mode 100644 index 00000000000..cd0a4ebc065 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml @@ -0,0 +1,202 @@ +parameters: + - name: RUN_WINDOWS + type: boolean + default: true + - name: RUN_TEST_AND_RELEASE + type: boolean + default: true + - name: OfficialBuild + type: boolean + +stages: +- stage: prep + jobs: + - job: SetVars + displayName: Set Variables + pool: + type: linux + + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' + - name: ob_sdl_codeSignValidation_enabled + value: false + - name: ob_sdl_codeql_compiled_enabled + value: false + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_sbom_enabled + value: false + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment variables + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + +- stage: macos + displayName: macOS - build and sign + dependsOn: ['prep'] + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: x64 + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: arm64 + +- stage: linux + displayName: linux - build and sign + dependsOn: ['prep'] + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64_minSize' + BuildConfiguration: 'minSize' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm' + JobName: 'linux_arm' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm64' + JobName: 'linux_arm64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-x64' + JobName: 'linux_fxd_x64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-arm64' + JobName: 'linux_fxd_arm64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-noopt-linux-musl-x64' + JobName: 'linux_fxd_x64_alpine' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent' + JobName: 'linux_fxd' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-musl-x64' + JobName: 'linux_x64_alpine' + +- stage: windows + displayName: windows - build and sign + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_WINDOWS }}','true')) + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: release + JobName: build_windows_x64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: minSize + JobName: build_windows_x64_minSize_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x86 + JobName: build_windows_x86_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: arm64 + JobName: build_windows_arm64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependent + JobName: build_windows_fxdependent_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependentWinDesktop + JobName: build_windows_fxdependentWinDesktop_release + +- stage: test_and_release_artifacts + displayName: Test and Release Artifacts + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_TEST_AND_RELEASE }}','true')) + jobs: + - template: /.pipelines/templates/testartifacts.yml@self + + - job: release_json + displayName: Create and Upload release.json + pool: + type: windows + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + steps: + - checkout: self + clean: true + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + - template: /.pipelines/templates/rebuild-branch-check.yml@self + - powershell: | + $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't mark as LTS release for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, not marking as LTS release" -Verbose + } + + @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" + Get-Content "$(Build.StagingDirectory)\release.json" + + if (-not (Test-Path "$(ob_outputDirectory)\metadata")) { + New-Item -ItemType Directory -Path "$(ob_outputDirectory)\metadata" + } + + Copy-Item -Path "$(Build.StagingDirectory)\release.json" -Destination "$(ob_outputDirectory)\metadata" -Force + displayName: Create and upload release.json file to build artifact + retryCountOnTaskFailure: 2 + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/stages/PowerShell-Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml new file mode 100644 index 00000000000..ff40941e31b --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml @@ -0,0 +1,186 @@ +parameters: + - name: OfficialBuild + type: boolean + +stages: +- stage: prep + displayName: 'Prep BuildInfo+Az' + jobs: + - template: /.pipelines/templates/checkAzureContainer.yml@self + +- stage: mac_package + displayName: 'macOS Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: x64 + + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: arm64 + +- stage: windows_package_build + displayName: 'Win Pkg (unsigned)' + dependsOn: [] + jobs: + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: minsize + +- stage: windows_package_sign + displayName: 'Win Pkg Sign' + dependsOn: [windows_package_build] + jobs: + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: minsize + +- stage: linux_package + displayName: 'Linux Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: deb + jobName: deb + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_x64_mariner' + packageType: rpm-fxdependent #mariner-x64 + jobName: mariner_x64 + signingProfile: 'CP-459159-pgpdetached' + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_arm64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_arm64_mariner' + packageType: rpm-fxdependent-arm64 #mariner-arm64 + jobName: mariner_arm64 + signingProfile: 'CP-459159-pgpdetached' + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: rpm + jobName: rpm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm' + signedDrop: 'drop_linux_sign_linux_arm' + packageType: tar-arm + jobName: tar_arm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm64' + signedDrop: 'drop_linux_sign_linux_arm64' + packageType: tar-arm64 + jobName: tar_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_alpine' + signedDrop: 'drop_linux_sign_linux_x64_alpine' + packageType: tar-alpine + jobName: tar_alpine + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd' + signedDrop: 'drop_linux_sign_linux_fxd' + packageType: fxdependent + jobName: fxdependent + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: tar + jobName: tar + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_alpine' + signedDrop: 'drop_linux_sign_linux_fxd_x64_alpine' + packageType: tar-alpine-fxdependent + jobName: tar_alpine_fxd + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_minSize' + signedDrop: 'drop_linux_sign_linux_x64_minSize' + packageType: min-size + jobName: minSize + +- stage: nupkg + displayName: 'NuGet Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/nupkg.yml@self + +- stage: msixbundle + displayName: 'MSIX Bundle+Sign' + dependsOn: [windows_package_build] # Only depends on unsigned packages + jobs: + - template: /.pipelines/templates/package-create-msix.yml@self + parameters: + OfficialBuild: ${{ parameters.OfficialBuild }} + +- stage: upload + displayName: 'Upload' + dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON + jobs: + - template: /.pipelines/templates/uploadToAzure.yml@self + +- stage: validatePackages + displayName: 'Validate Packages' + dependsOn: [upload] + jobs: + - template: /.pipelines/templates/release-validate-packagenames.yml@self diff --git a/.pipelines/templates/stages/PowerShell-Release-Stages.yml b/.pipelines/templates/stages/PowerShell-Release-Stages.yml new file mode 100644 index 00000000000..52ce428a663 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Release-Stages.yml @@ -0,0 +1,323 @@ +parameters: + - name: releaseEnvironment + type: string + - name: SkipPublish + type: boolean + - name: SkipPSInfraInstallers + type: boolean + - name: skipMSIXPublish + type: boolean + +stages: +- stage: setReleaseTagAndChangelog + displayName: 'Set Release Tag and Upload Changelog' + jobs: + - template: /.pipelines/templates/release-SetTagAndChangelog.yml@self + +- stage: validateSdk + displayName: 'Validate SDK' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "windowsSDK" + displayName: "Windows SDK Validation" + imageName: PSMMS2019-Secure + poolName: $(windowsPool) + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "MacOSSDK" + displayName: "MacOS SDK Validation" + imageName: macOS-latest + poolName: Azure Pipelines + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "LinuxSDK" + displayName: "Linux SDK Validation" + imageName: PSMMSUbuntu22.04-Secure + poolName: $(ubuntuPool) + +- stage: gbltool + displayName: 'Validate Global tools' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "WindowsGlobalTools" + displayName: "Windows Global Tools Validation" + jobtype: windows + + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "LinuxGlobalTools" + displayName: "Linux Global Tools Validation" + jobtype: linux + globalToolExeName: 'pwsh' + globalToolPackageName: 'PowerShell.Linux.x64' + +- stage: fxdpackages + displayName: 'Validate FXD Packages' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxd' + displayName: 'Validate Win Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependent' + packageNamePattern: '**/*win-fxdependent.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxdDesktop' + displayName: 'Validate WinDesktop Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependentWinDesktop' + packageNamePattern: '**/*win-fxdependentwinDesktop.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxfxd' + displayName: 'Validate Linux Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxArm64fxd' + displayName: 'Validate Linux ARM64 Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + # this is really an architecture independent package + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + arm64: 'yes' + enableCredScan: false + +- stage: ManualValidation + dependsOn: [] + displayName: Manual Validation + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate Windows Packages + jobName: ValidateWinPkg + instructions: | + Validate zip package on windows + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate OSX Packages + jobName: ValidateOsxPkg + instructions: | + Validate tar.gz package on osx-arm64 + +- stage: ReleaseAutomation + dependsOn: [] + displayName: 'Release Automation' + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start Release Automation + jobName: StartRA + instructions: | + Kick off Release automation build at: https://dev.azure.com/powershell-rel/Release-Automation/_build?definitionId=10&_a=summary + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Triage results + jobName: TriageRA + dependsOnJob: StartRA + instructions: | + Triage ReleaseAutomation results + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Signoff Tests + dependsOnJob: TriageRA + jobName: SignoffTests + instructions: | + Signoff ReleaseAutomation results + +- stage: UpdateChangeLog + displayName: Update the changelog + dependsOn: + - ManualValidation + - ReleaseAutomation + - fxdpackages + - gbltool + - validateSdk + + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure the changelog is updated + jobName: MergeChangeLog + instructions: | + Update and merge the changelog for the release. + This step is required for creating GitHub draft release. + +- stage: PublishGitHubReleaseAndNuget + displayName: Publish GitHub and Nuget Release + dependsOn: + - setReleaseTagAndChangelog + - UpdateChangeLog + variables: + ob_release_environment: ${{ parameters.releaseEnvironment }} + jobs: + - template: /.pipelines/templates/release-githubNuget.yml@self + parameters: + skipPublish: ${{ parameters.SkipPublish }} + +- stage: PushGitTagAndMakeDraftPublic + displayName: Push Git Tag and Make Draft Public + dependsOn: PublishGitHubReleaseAndNuget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Push Git Tag + jobName: PushGitTag + instructions: | + Push the git tag to upstream + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make Draft Public + dependsOnJob: PushGitTag + jobName: DraftPublic + instructions: | + Make the GitHub Release Draft Public + +- stage: BlobPublic + displayName: Make Blob Public + dependsOn: + - UpdateChangeLog + - PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/release-MakeBlobPublic.yml@self + parameters: + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + +- stage: PublishPMC + displayName: Publish PMC + dependsOn: PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Publish to PMC + jobName: ReleaseToPMC + instructions: | + Run PowerShell-Release-Official-Azure.yml pipeline to publish to PMC + +- stage: UpdateDotnetDocker + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Update DotNet SDK Docker images + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Update .NET SDK docker images + jobName: DotnetDocker + instructions: | + Create PR for updating dotnet-docker images to use latest PowerShell version. + 1. Fork and clone https://github.com/dotnet/dotnet-docker.git + 2. git checkout upstream/nightly -b updatePS + 3. dotnet run --project .\eng\update-dependencies\ specific --product-version powershell= --compute-shas + 4. create PR targeting nightly branch + +- stage: UpdateWinGet + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Add manifest entry to winget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Add manifest entry to winget + jobName: UpdateWinGet + instructions: | + This is typically done by the community 1-2 days after the release. + +- stage: PublishMsix + dependsOn: + - setReleaseTagAndChangelog + - PushGitTagAndMakeDraftPublic + displayName: Publish MSIX to store + variables: + ob_release_environment: ${{ parameters.releaseEnvironment }} + jobs: + - template: /.pipelines/templates/release-MSIX-Publish.yml@self + parameters: + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} + +- stage: PublishVPack + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release vPack + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start 2 vPack Release pipelines + jobName: PublishVPack + instructions: | + 1. Kick off PowerShell-vPack-Official pipeline + 2. Kick off PowerShell-MSIXBundle-VPack pipeline + +# Need to verify if the Az PS / CLI team still uses this. Skipping for this release. +# - stage: ReleaseDeps +# dependsOn: GitHubTasks +# displayName: Update pwsh.deps.json links +# jobs: +# - template: templates/release-UpdateDepsJson.yml + +- stage: UploadBuildInfoJson + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Upload BuildInfo.json + jobs: + - template: /.pipelines/templates/release-upload-buildinfo.yml@self + +- stage: ReleaseSymbols + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release Symbols + jobs: + - template: /.pipelines/templates/release-symbols.yml@self + +- stage: ChangesToMaster + displayName: Ensure changes are in GH master + dependsOn: + - PublishPMC + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure changes are in master + jobName: MergeToMaster + instructions: | + Make sure that changes README.md and metadata.json are merged into master on GitHub. + +- stage: ReleaseToMU + displayName: Release to MU + dependsOn: PushGitTagAndMakeDraftPublic # This only needs the blob to be available + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Release to MU + instructions: | + Notify the PM team to start the process of releasing to MU. + +- stage: ReleaseClose + displayName: Finish Release + dependsOn: + - ReleaseToMU + - ReleaseSymbols + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Retain Build + jobName: RetainBuild + instructions: | + Retain the build + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Delete release branch + jobName: DeleteBranch + instructions: | + Delete release branch diff --git a/.pipelines/templates/stages/PowerShell-vPack-Stages.yml b/.pipelines/templates/stages/PowerShell-vPack-Stages.yml new file mode 100644 index 00000000000..f0d49e8b489 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-vPack-Stages.yml @@ -0,0 +1,236 @@ +parameters: + - name: createVPack + type: boolean + - name: vPackName + type: string + +stages: +- stage: BuildStage + jobs: + - job: BuildJob + pool: + type: windows + + strategy: + matrix: + x86: + architecture: x86 + + x64: + architecture: x64 + + arm64: + architecture: arm64 + + variables: + ArtifactPlatform: 'windows' + ob_artifactBaseName: drop_build_$(architecture) + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_owneralias: tplunk + ob_createvpack_versionAs: parts + ob_createvpack_propsFile: true + ob_createvpack_verbose: true + ob_createvpack_packagename: '${{ parameters.vPackName }}.$(architecture)' + ob_createvpack_description: PowerShell $(architecture) $(version) + # I think the variables reload after we transition back to the host so this works. 🤷‍♂️ + ob_createvpack_majorVer: $(pwshMajorVersion) + ob_createvpack_minorVer: $(pwshMinorVersion) + ob_createvpack_patchVer: $(pwshPatchVersion) + ${{ if ne(variables['pwshPrereleaseVersion'], '') }}: + ob_createvpack_prereleaseVer: $(pwshPrereleaseVersion) + ${{ else }}: + ob_createvpack_prereleaseVer: $(Build.SourceVersion) + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s + env: + ob_restore_phase: true + + - template: .pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + + - pwsh: | + $version = '$(Version)' + Write-Verbose -Verbose "Version: $version" + if(!$version) { + throw "Version is not set." + } + + $mainVersionParts = $version -split '-' + + Write-Verbose -Verbose "mainVersionParts: $($mainVersionParts[0]) ; $($mainVersionParts[1])" + $versionParts = $mainVersionParts[0] -split '[.]'; + $major = $versionParts[0] + $minor = $versionParts[1] + $patch = $versionParts[2] + + $previewPart = $mainVersionParts[1] + Write-Verbose -Verbose "previewPart: $previewPart" + + Write-Host "major: $major; minor: $minor; patch: $patch;" + + $vstsCommandString = "vso[task.setvariable variable=pwshMajorVersion]$major" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=pwshMinorVersion]$minor" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=pwshPatchVersion]$patch" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + if($previewPart) { + $vstsCommandString = "vso[task.setvariable variable=pwshPrereleaseVersion]$previewPart" + } else { + Write-Verbose -Verbose "No prerelease part found in version string." + } + displayName: Set ob_createvpack_*Ver + env: + ob_restore_phase: true + + # Validate pwsh*Version variables + - pwsh: | + $variables = @("pwshMajorVersion", "pwshMinorVersion", "pwshPatchVersion") + foreach ($var in $variables) { + if (-not (get-item "Env:\$var" -ErrorAction SilentlyContinue).value) { + throw "Required variable '`$env:$var' is not set." + } + } + displayName: Validate pwsh*Version variables + env: + ob_restore_phase: true + + - pwsh: | + if($env:RELEASETAGVAR -match '-') { + throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" + } + displayName: Stop any preview release + env: + ob_restore_phase: true + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + version: 3.1.x + installationPath: $(Agent.ToolsDirectory)/dotnet + + ### BUILD ### + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(repoRoot) + + - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + inputs: + Enabled: true + AnalyzeInPipeline: false # Do not upload results + Language: csharp + + - task: UseDotNet@2 + displayName: 'Install .NET based on global.json' + inputs: + useGlobalJson: true + workingDirectory: $(repoRoot) + env: + ob_restore_phase: true + + - pwsh: | + # Need to set PowerShellRoot variable for obp-file-signing template + $vstsCommandString = "vso[task.setvariable variable=PowerShellRoot]$(repoRoot)" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $Architecture = '$(Architecture)' + $runtime = switch ($Architecture) + { + "x64" { "win7-x64" } + "x86" { "win7-x86" } + "arm64" { "win-arm64" } + } + + $params = @{} + if ($env:BuildConfiguration -eq 'minSize') { + $params['ForMinimalSize'] = $true + } + + $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" + Import-Module -Name $(repoRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/Symbols_$Architecture" -Force + + Start-PSBootstrap -Scenario Package + $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose + + $ReleaseTagParam = @{} + + if ($env:RELEASETAGVAR) { + $ReleaseTagParam['ReleaseTag'] = $env:RELEASETAGVAR + } + + Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore @params @ReleaseTagParam + + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $psOptPath = "$outputPath/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" + displayName: Build Windows Universal - $(Architecture) -$(BuildConfiguration) Symbols folder + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: CodeQL3000Finalize@0 # Add CodeQL Finalize task right after your 'Build' step. + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + inputs: + sourceScanPath: '$(repoRoot)\src' + ob_restore_phase: true + + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + SigningProfile: $(windows_build_tools_cert_id) + OfficialBuild: false + vPackScenario: true + + ### END OF BUILD ### + + - pwsh: | + Get-ChildItem env:/ob_createvpack_*Ver + Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse + Get-Content "$(Pipeline.Workspace)\PowerShell\preview.json" -ErrorAction SilentlyContinue | Write-Host + displayName: Debug Output Directory and Version + condition: succeededOrFailed() + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - pwsh: | + $vpackFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(Pipeline.Workspace)\Symbols_$(Architecture)" + } + $vpackFiles + displayName: Debug Output Directory and Version + condition: succeededOrFailed() diff --git a/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml new file mode 100644 index 00000000000..de3ac0ba1b6 --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml @@ -0,0 +1,67 @@ +parameters: + - name: InternalSDKBlobURL + type: string + default: ' ' + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + type: string + default: 'NO' + - name: ENABLE_MSBUILD_BINLOGS + type: boolean + default: false + - name: FORCE_CODEQL + type: boolean + default: false + +variables: + - name: PS_RELEASE_BUILD + value: 1 + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - name: BUILDSECMON_OPT_IN + value: true + - name: __DOTNET_RUNTIME_FEED + value: ${{ parameters.InternalSDKBlobURL }} + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - name: WindowsContainerImage + value: onebranch.azurecr.io/windows/ltsc2019/vse2022:latest + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: SKIP_SIGNING + value: ${{ parameters.SKIP_SIGNING }} + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: ENABLE_MSBUILD_BINLOGS + value: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: + # Cadence is hours before CodeQL will allow a re-upload of the database + - name: CodeQL.Cadence + value: 1 + - name: CODEQL_ENABLED + ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: + value: true + ${{ else }}: + value: false + # Fix for BinSkim ICU package error in Linux containers + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: true + # Disable BinSkim at job level to override NonOfficial template defaults + - name: ob_sdl_binskim_enabled + value: false diff --git a/.pipelines/templates/variables/PowerShell-Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Packages-Variables.yml new file mode 100644 index 00000000000..7d1818909b5 --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Packages-Variables.yml @@ -0,0 +1,50 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ForceAzureBlobDelete + type: string + default: 'false' + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: disableNetworkIsolation + type: boolean + default: false + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] # needed for onebranch.pipeline.version task + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: ForceAzureBlobDelete + value: ${{ parameters.ForceAzureBlobDelete }} + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - group: MSIXSigningProfile + - name: disableNetworkIsolation + value: ${{ parameters.disableNetworkIsolation }} diff --git a/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml b/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml new file mode 100644 index 00000000000..3b47e5eff2b --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml @@ -0,0 +1,35 @@ +parameters: + - name: debug + type: boolean + default: false + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\.config\tsaoptions.json + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - group: PoolNames diff --git a/.pipelines/templates/variables/PowerShell-Release-Variables.yml b/.pipelines/templates/variables/PowerShell-Release-Variables.yml new file mode 100644 index 00000000000..930c559eafe --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Release-Variables.yml @@ -0,0 +1,41 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ReleaseTagVar + type: string + default: 'fromBranch' + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - group: PoolNames + # Fix for BinSkim ICU package error in Linux containers + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: true diff --git a/.pipelines/templates/variables/PowerShell-vPack-Variables.yml b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml new file mode 100644 index 00000000000..276911a35b3 --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml @@ -0,0 +1,39 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: netiso + type: string + default: 'R1' + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: BuildSolution + value: $(Build.SourcesDirectory)\dirs.proj + - name: BuildConfiguration + value: Release + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + - name: Codeql.Enabled + value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - group: Azure Blob variable group + - group: certificate_logical_to_actual # used within signing task + - group: DotNetPrivateBuildAccess + - name: netiso + value: ${{ parameters.netiso }} +# We shouldn't be using PATs anymore +# - group: mscodehub-feed-read-general diff --git a/.pipelines/templates/variable/release-shared.yml b/.pipelines/templates/variables/release-shared.yml similarity index 100% rename from .pipelines/templates/variable/release-shared.yml rename to .pipelines/templates/variables/release-shared.yml From a17f1761eca57d90856062e35add0f013a1c703f Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:56:16 -0500 Subject: [PATCH 233/255] Select New MSIX Package Name (#27096) Co-authored-by: Justin Chung --- .pipelines/templates/packaging/windows/package.yml | 2 +- .pipelines/templates/packaging/windows/sign.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml index 92926e6aa59..1cc0e9d0e94 100644 --- a/.pipelines/templates/packaging/windows/package.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -220,7 +220,7 @@ jobs: } if ($packageTypes -contains 'msix') { - $msixPkgNameFilter = "powershell-*.msix" + $msixPkgNameFilter = "PowerShell*.msix" $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName Write-Verbose -Verbose "unsigned msixPkgPath: $msixPkgPath" Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose diff --git a/.pipelines/templates/packaging/windows/sign.yml b/.pipelines/templates/packaging/windows/sign.yml index 4a095ba7694..f7a2e5e03e8 100644 --- a/.pipelines/templates/packaging/windows/sign.yml +++ b/.pipelines/templates/packaging/windows/sign.yml @@ -202,7 +202,7 @@ jobs: } if ($packageTypes -contains 'msix') { - $msixPkgNameFilter = "powershell-*.msix" + $msixPkgNameFilter = "PowerShell*.msix" $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName Write-Verbose -Verbose "signed msixPkgPath: $msixPkgPath" Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose From 79526d2460d9df3b0e6bdd254cdd577d26ffe4a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 10:06:36 -0700 Subject: [PATCH 234/255] Bump github/codeql-action from 4.34.1 to 4.35.1 (#27120) --- .github/workflows/analyze-reusable.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index dfe565b0319..0fe64afb19a 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v3.29.5 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,4 +74,4 @@ jobs: shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v3.29.5 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 7bd7bf6314d..b84ed640102 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v3.29.5 + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 with: sarif_file: results.sarif From 27c53eaf4508d22dc6ae46841d6a1823ba07cd6b Mon Sep 17 00:00:00 2001 From: cui Date: Wed, 1 Apr 2026 01:08:09 +0800 Subject: [PATCH 235/255] PSStyle: validate background index against `BackgroundColorMap` (#27106) --- .../FormatAndOutput/common/PSStyle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs index 3f927afd7d5..534f9541195 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -892,7 +892,7 @@ public static string MapColorPairToEscapeSequence(ConsoleColor foregroundColor, throw new ArgumentOutOfRangeException(paramName: nameof(foregroundColor)); } - if (backIndex < 0 || backIndex >= ForegroundColorMap.Length) + if (backIndex < 0 || backIndex >= BackgroundColorMap.Length) { throw new ArgumentOutOfRangeException(paramName: nameof(backgroundColor)); } From f3fcee88f19150cd3561d625e484e384ad80648a Mon Sep 17 00:00:00 2001 From: scuzqy <80660355+scuzqy@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:17:39 +0800 Subject: [PATCH 236/255] Fix `Remove-Item` confirmation message to use provider path instead (#27123) --- .../commands/management/Navigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index f4ba55202ed..6ab8f1d652d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -2718,7 +2718,7 @@ protected override void ProcessRecord() { // Get the localized prompt string - string prompt = StringUtil.Format(NavigationResources.RemoveItemWithChildren, resolvedPath.Path); + string prompt = StringUtil.Format(NavigationResources.RemoveItemWithChildren, providerPath); // Confirm the user wants to remove all children and the item even if // they did not specify -recurse From 30f70ee772e70278290ba5c7e6a4bb46a59fd184 Mon Sep 17 00:00:00 2001 From: reabr <91016230+reabr@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:52:55 +0330 Subject: [PATCH 237/255] Add verbose message to `Get-Service` when properties cannot be returned (#27109) --- .../commands/management/Service.cs | 36 +++++++++++- .../resources/ServiceResources.resx | 57 ++++++++++--------- 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 739baa15bab..a00f9583d6a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -674,13 +674,30 @@ protected override void ProcessRecord() #endregion Overrides #nullable enable + + /// + /// Writes a verbose message when a service property query fails. + /// + /// Name of the service. + /// Name of the property that failed to be queried. + private void WriteServicePropertyError(string serviceName, string propertyName) + { + Win32Exception e = new(Marshal.GetLastWin32Error()); + WriteVerbose( + StringUtil.Format( + ServiceResources.CouldNotGetServiceProperty, + serviceName, + propertyName, + e.Message)); + } + /// /// Adds UserName, Description, BinaryPathName, DelayedAutoStart and StartupType to a ServiceController object. /// /// Handle to the local SCManager instance. /// /// ServiceController as PSObject with UserName, Description and StartupType added. - private static PSObject AddProperties(nint scManagerHandle, ServiceController service) + private PSObject AddProperties(nint scManagerHandle, ServiceController service) { NakedWin32Handle hService = nint.Zero; @@ -709,6 +726,10 @@ private static PSObject AddProperties(nint scManagerHandle, ServiceController se { description = descriptionInfo.lpDescription; } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DESCRIPTIONW)); + } if (NativeMethods.QueryServiceConfig2( hService, @@ -717,6 +738,10 @@ private static PSObject AddProperties(nint scManagerHandle, ServiceController se { isDelayedAutoStart = autostartInfo.fDelayedAutostart; } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + } if (NativeMethods.QueryServiceConfig( hService, @@ -731,6 +756,15 @@ private static PSObject AddProperties(nint scManagerHandle, ServiceController se isDelayedAutoStart.Value); } } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.QUERY_SERVICE_CONFIG)); + } + } + else + { + // handle when OpenServiceW itself fails: + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_QUERY_CONFIG)); } } finally diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index e1213c3fb05..c19f753529f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -1,17 +1,17 @@ - @@ -213,4 +213,7 @@ The startup type '{0}' is not supported by {1}. + + Could not retrieve property '{1}' for service '{0}': {2} + From 1216cb7bedf37c0763acae0180d9bbb3118b881e Mon Sep 17 00:00:00 2001 From: Staffan Gustafsson Date: Tue, 31 Mar 2026 20:35:05 +0200 Subject: [PATCH 238/255] Add comment-based help documentation to `build.psm1` functions (#27122) --- build.psm1 | 847 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 822 insertions(+), 25 deletions(-) diff --git a/build.psm1 b/build.psm1 index ec91f2cf314..737b3e6182f 100644 --- a/build.psm1 +++ b/build.psm1 @@ -35,6 +35,16 @@ $tagsUpToDate = $false # This function is used during the setup phase in tools/ci.psm1 function Sync-PSTags { + <# + .SYNOPSIS + Syncs git tags from the PowerShell/PowerShell upstream remote. + .DESCRIPTION + Ensures that tags from the PowerShell/PowerShell upstream remote have been fetched. + Functions like Get-PSCommitId and Get-PSLatestTag require tags to be current. + This is called during the setup phase in tools/ci.psm1. + .PARAMETER AddRemoteIfMissing + If specified, adds the upstream remote automatically when it is not present. + #> param( [Switch] $AddRemoteIfMissing @@ -78,6 +88,15 @@ function Sync-PSTags # Gets the latest tag for the current branch function Get-PSLatestTag { + <# + .SYNOPSIS + Gets the latest git tag reachable from the current HEAD. + .DESCRIPTION + Returns the most recent annotated git tag. Run Sync-PSTags first to ensure tags + are up to date; otherwise a warning is emitted. + .OUTPUTS + System.String. The latest tag string, e.g. 'v7.5.0'. + #> [CmdletBinding()] param() # This function won't always return the correct value unless tags have been sync'ed @@ -92,6 +111,17 @@ function Get-PSLatestTag function Get-PSVersion { + <# + .SYNOPSIS + Returns the PowerShell version string for the current commit. + .DESCRIPTION + Derives the version from the latest git tag, optionally omitting the commit-ID suffix. + .PARAMETER OmitCommitId + When specified, returns only the bare version (e.g. '7.5.0') from the latest tag, + without the commit-count and hash suffix appended by git describe. + .OUTPUTS + System.String. A version string such as '7.5.0' or '7.5.0-15-gabcdef1234'. + #> [CmdletBinding()] param( [switch] @@ -109,6 +139,16 @@ function Get-PSVersion function Get-PSCommitId { + <# + .SYNOPSIS + Returns the PowerShell commit-ID string produced by git describe. + .DESCRIPTION + Returns the full git describe string including the tag, number of commits since + the tag, and the abbreviated commit hash (e.g. 'v7.5.0-15-gabcdef1234567890'). + Run Sync-PSTags first; otherwise a warning is emitted. + .OUTPUTS + System.String. A git describe string such as 'v7.5.0-15-gabcdef1234567890'. + #> [CmdletBinding()] param() # This function won't always return the correct value unless tags have been sync'ed @@ -123,6 +163,19 @@ function Get-PSCommitId function Get-EnvironmentInformation { + <# + .SYNOPSIS + Collects information about the current operating environment. + .DESCRIPTION + Returns a PSCustomObject containing OS-identity flags, architecture, admin status, + NuGet package root paths, and Linux distribution details. The object is used + throughout the build module to make platform-conditional decisions. + .OUTPUTS + System.Management.Automation.PSCustomObject. An object with properties such as + IsWindows, IsLinux, IsMacOS, IsAdmin, OSArchitecture, and distribution-specific flags + (IsUbuntu, IsDebian, IsRedHatFamily, etc.). + #> + param() $environment = @{'IsWindows' = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT} # PowerShell will likely not be built on pre-1709 nanoserver if ('System.Management.Automation.Platform' -as [type]) { @@ -281,6 +334,54 @@ function Test-IsReleaseCandidate $optimizedFddRegex = 'fxdependent-(linux|win|win7|osx)-(x64|x86|arm64|arm)' function Start-PSBuild { + <# + .SYNOPSIS + Builds PowerShell from source using dotnet publish. + .DESCRIPTION + Compiles the PowerShell source tree for the specified runtime and configuration. + Optionally restores NuGet packages, regenerates resources, generates the type catalog, + and restores Gallery modules. Saves build options so subsequent commands can reuse them. + .PARAMETER StopDevPowerShell + Stops any running dev pwsh process before building to prevent file-in-use errors. + .PARAMETER Restore + Forces NuGet package restore even when packages already exist. + .PARAMETER Output + Path to the output directory. Defaults to the standard build location. + .PARAMETER ResGen + Regenerates C# bindings for resx resource files before building. + .PARAMETER TypeGen + Regenerates the CorePsTypeCatalog.cs type-catalog file before building. + .PARAMETER Clean + Runs 'git clean -fdX' to remove untracked and ignored files before building. + .PARAMETER PSModuleRestore + Restores PowerShell Gallery modules to the build output directory (legacy parameter set). + .PARAMETER NoPSModuleRestore + Skips restoring PowerShell Gallery modules to the build output directory. + .PARAMETER CI + Indicates a CI build; restores the Pester module to the output directory. + .PARAMETER ForMinimalSize + Produces a build optimized for minimal binary size (linux-x64, win7-x64, or osx-x64 only). + .PARAMETER SkipExperimentalFeatureGeneration + Skips the step that runs the built pwsh to produce the experimental-features list. + .PARAMETER SMAOnly + Rebuilds only System.Management.Automation.dll for rapid engine iteration. + .PARAMETER UseNuGetOrg + Uses nuget.org instead of the private PowerShell feed for package restore. + .PARAMETER Runtime + The .NET runtime identifier (RID) to target, e.g. 'win7-x64' or 'linux-x64'. + .PARAMETER Configuration + The build configuration: Debug, Release, CodeCoverage, or StaticAnalysis. + .PARAMETER ReleaseTag + A git tag in 'vX.Y.Z[-preview.N|-rc.N]' format to embed as the release version. + .PARAMETER Detailed + Passes '--verbosity d' to dotnet for detailed build output. + .PARAMETER InteractiveAuth + Passes '--interactive' to dotnet restore for interactive feed authentication. + .PARAMETER SkipRoslynAnalyzers + Skips Roslyn analyzer execution during the build. + .PARAMETER PSOptionsPath + When supplied, saves the resolved build options to this JSON file path. + #> [CmdletBinding(DefaultParameterSetName="Default")] param( # When specified this switch will stops running dev powershell @@ -763,6 +864,20 @@ Fix steps: } function Switch-PSNugetConfig { + <# + .SYNOPSIS + Switches the NuGet configuration between public, private, and NuGet.org-only sources. + .DESCRIPTION + Regenerates nuget.config files in the repository root, src/Modules, and test/tools/Modules + to point to the specified feed source. Optionally stores authenticated credentials. + .PARAMETER Source + The feed set to activate: 'Public' (nuget.org + dotnet feed), 'Private' (PowerShell ADO + feed), or 'NuGetOnly' (nuget.org only). + .PARAMETER UserName + Username for authenticated private feed access. + .PARAMETER ClearTextPAT + Personal access token in clear text for authenticated private feed access. + #> param( [Parameter(Mandatory = $true, ParameterSetName = 'user')] [Parameter(Mandatory = $true, ParameterSetName = 'nouser')] @@ -814,6 +929,18 @@ function Switch-PSNugetConfig { function Test-ShouldGenerateExperimentalFeatures { + <# + .SYNOPSIS + Determines whether experimental-feature JSON files should be generated on this host. + .DESCRIPTION + Returns $true only when the current runtime identifier matches the host OS and + architecture, the build is not a release build (PS_RELEASE_BUILD not set), and the + runtime is not fxdependent. + .PARAMETER Runtime + The .NET runtime identifier (RID) being targeted by the build. + .OUTPUTS + System.Boolean. $true if the experimental-feature list should be generated. + #> param( [Parameter(Mandatory)] $Runtime @@ -853,6 +980,23 @@ function Test-ShouldGenerateExperimentalFeatures function Restore-PSPackage { + <# + .SYNOPSIS + Restores NuGet packages for the PowerShell project directories. + .DESCRIPTION + Runs 'dotnet restore' on the main PowerShell project directories with up to five + retries on transient failures. Honors the target runtime identifier and build verbosity. + .PARAMETER ProjectDirs + Explicit list of project directories to restore. Defaults to the standard PS project set. + .PARAMETER Options + PSOptions object specifying runtime and configuration. Defaults to Get-PSOptions. + .PARAMETER Force + Forces restore even when project.assets.json already exists. + .PARAMETER InteractiveAuth + Passes '--interactive' to dotnet restore for interactive feed authentication. + .PARAMETER PSModule + Restores in PSModule mode, omitting the runtime argument. + #> [CmdletBinding()] param( [ValidateNotNullOrEmpty()] @@ -967,6 +1111,16 @@ function Restore-PSPackage function Restore-PSModuleToBuild { + <# + .SYNOPSIS + Copies PowerShell Gallery modules from the NuGet cache into the build output Modules folder. + .DESCRIPTION + Resolves Gallery module packages referenced in PSGalleryModules.csproj and copies + them to the Modules subdirectory of the specified publish path. Also removes + .nupkg.metadata files left behind by the restore. + .PARAMETER PublishPath + The PowerShell build output directory whose Modules sub-folder receives the modules. + #> param( [Parameter(Mandatory)] [string] @@ -983,6 +1137,14 @@ function Restore-PSModuleToBuild function Restore-PSPester { + <# + .SYNOPSIS + Downloads and saves the Pester module (v4.x) from the PowerShell Gallery. + .DESCRIPTION + Uses Save-Module to install Pester up to version 4.99 into the target directory. + .PARAMETER Destination + Directory to save Pester into. Defaults to the Modules folder of the current build output. + #> param( [ValidateNotNullOrEmpty()] [string] $Destination = ([IO.Path]::Combine((Split-Path (Get-PSOptions -DefaultToNew).Output), "Modules")) @@ -991,6 +1153,15 @@ function Restore-PSPester } function Compress-TestContent { + <# + .SYNOPSIS + Compresses the test directory into a zip archive for distribution. + .DESCRIPTION + Publishes PSTestTools and then zips the entire test/ directory to the given + destination path using System.IO.Compression.ZipFile. + .PARAMETER Destination + The path of the output zip file to create. + #> [CmdletBinding()] param( $Destination @@ -1005,6 +1176,30 @@ function Compress-TestContent { } function New-PSOptions { + <# + .SYNOPSIS + Creates a new PSOptions hashtable describing a PowerShell build configuration. + .DESCRIPTION + Computes the output path, project directory, and framework for a PowerShell build + based on the supplied runtime and configuration. The resulting hashtable is consumed + by Start-PSBuild, Restore-PSPackage, and related functions. + .PARAMETER Configuration + The build configuration: Debug (default), Release, CodeCoverage, or StaticAnalysis. + .PARAMETER Framework + The target .NET framework moniker. Defaults to 'net11.0'. + .PARAMETER Runtime + The .NET runtime identifier (RID). Detected automatically via 'dotnet --info' if omitted. + .PARAMETER Output + Optional path to the output directory. The executable name is appended automatically. + .PARAMETER SMAOnly + Targets only the System.Management.Automation project rather than the full host. + .PARAMETER PSModuleRestore + Indicates whether Start-PSBuild should restore PowerShell Gallery modules. + .PARAMETER ForMinimalSize + Produces a build targeting minimal binary size. + .OUTPUTS + System.Collections.Hashtable. A hashtable with build option properties. + #> [CmdletBinding()] param( [ValidateSet('Debug', 'Release', 'CodeCoverage', 'StaticAnalysis', '')] @@ -1152,6 +1347,17 @@ function New-PSOptions { # Get the Options of the last build function Get-PSOptions { + <# + .SYNOPSIS + Returns the PSOptions from the most recent Start-PSBuild call. + .DESCRIPTION + Retrieves the script-level $script:Options object. If no build has been run and + -DefaultToNew is specified, returns a fresh object from New-PSOptions. + .PARAMETER DefaultToNew + When specified, returns default options from New-PSOptions if no build has occurred. + .OUTPUTS + System.Collections.Hashtable. The current PSOptions hashtable, or $null. + #> param( [Parameter(HelpMessage='Defaults to New-PSOption if a build has not occurred.')] [switch] @@ -1167,6 +1373,15 @@ function Get-PSOptions { } function Set-PSOptions { + <# + .SYNOPSIS + Stores the supplied PSOptions as the active build options. + .DESCRIPTION + Writes the options hashtable to the script-scoped $script:Options variable, + making it available to subsequent Get-PSOptions calls. + .PARAMETER Options + The PSOptions hashtable to store. + #> param( [PSObject] $Options @@ -1176,6 +1391,17 @@ function Set-PSOptions { } function Get-PSOutput { + <# + .SYNOPSIS + Returns the path to the PowerShell executable produced by the build. + .DESCRIPTION + Looks up the Output path from the supplied options hashtable, the cached + script-level options, or a fresh New-PSOptions call, in that order of precedence. + .PARAMETER Options + An explicit options hashtable. If omitted, the most recent build options are used. + .OUTPUTS + System.String. The full path to the built pwsh or pwsh.exe executable. + #> [CmdletBinding()]param( [hashtable]$Options ) @@ -1189,6 +1415,21 @@ function Get-PSOutput { } function Get-PesterTag { + <# + .SYNOPSIS + Scans the Pester test tree and returns a summary of all tags in use. + .DESCRIPTION + Parses every *.tests.ps1 file under the specified base directory using the + PowerShell AST, validates that each Describe block has exactly one priority tag + (CI, Feature, or Scenario), and returns a summary object with tag counts and + any validation warnings. + .PARAMETER testbase + Root directory to search for test files. + Defaults to '$PSScriptRoot/test/powershell'. + .OUTPUTS + PSCustomObject (DescribeTagsInUse). Properties are tag names mapped to usage + counts, plus 'Result' (Pass/Fail) and 'Warnings' (string[]). + #> param ( [Parameter(Position=0)][string]$testbase = "$PSScriptRoot/test/powershell" ) $alltags = @{} $warnings = @() @@ -1255,6 +1496,13 @@ function Get-PesterTag { # testing PowerShell remote custom connections. function Publish-CustomConnectionTestModule { + <# + .SYNOPSIS + Builds and publishes the Microsoft.PowerShell.NamedPipeConnection test module. + .DESCRIPTION + Invokes the module's own build.ps1 script, copies the output to + test/tools/Modules, and then runs a clean build to remove intermediate artifacts. + #> Write-LogGroupStart -Title "Publish-CustomConnectionTestModule" $sourcePath = "${PSScriptRoot}/test/tools/NamedPipeConnection" $outPath = "${PSScriptRoot}/test/tools/NamedPipeConnection/out/Microsoft.PowerShell.NamedPipeConnection" @@ -1285,6 +1533,18 @@ function Publish-CustomConnectionTestModule } function Publish-PSTestTools { + <# + .SYNOPSIS + Builds and publishes all test tool projects to their bin directories. + .DESCRIPTION + Runs 'dotnet publish' for each test tool project (TestAlc, TestExe, UnixSocket, + WebListener, and on Windows TestService), copies Gallery test modules, and + publishes the NamedPipeConnection module. The tool bin directories are added to PATH + so that tests can locate the executables. + .PARAMETER runtime + The .NET runtime identifier (RID) used when publishing executables. + Defaults to the runtime from the current build options. + #> [CmdletBinding()] param( [string] @@ -1367,6 +1627,16 @@ function Publish-PSTestTools { } function Get-ExperimentalFeatureTests { + <# + .SYNOPSIS + Returns a mapping of experimental feature names to their associated test files. + .DESCRIPTION + Reads test/tools/TestMetadata.json and extracts the ExperimentalFeatures section, + returning a hashtable where keys are feature names and values are arrays of test paths. + .OUTPUTS + System.Collections.Hashtable. Keys are experimental feature names; values are + arrays of test file paths. + #> $testMetadataFile = Join-Path $PSScriptRoot "test/tools/TestMetadata.json" $metadata = Get-Content -Path $testMetadataFile -Raw | ConvertFrom-Json | ForEach-Object -MemberName ExperimentalFeatures $features = $metadata | Get-Member -MemberType NoteProperty | ForEach-Object -MemberName Name @@ -1379,6 +1649,57 @@ function Get-ExperimentalFeatureTests { } function Start-PSPester { + <# + .SYNOPSIS + Runs the Pester test suite against the built PowerShell. + .DESCRIPTION + Launches the built pwsh process with the Pester module and runs the specified + test paths. Automatically adjusts tag exclusions based on the current elevation + level, and emits NUnit XML results that are optionally published to Azure DevOps + or GitHub Actions. + .PARAMETER Path + One or more test file or directory paths to run. Defaults to test/powershell. + .PARAMETER OutputFormat + The Pester output format. Defaults to 'NUnitXml'. + .PARAMETER OutputFile + Path for the XML results file. Defaults to 'pester-tests.xml'. + .PARAMETER ExcludeTag + Tags to exclude from the run. Defaults to 'Slow'; adjusted for elevation level. + .PARAMETER Tag + Tags to include in the run. Defaults to 'CI' and 'Feature'. + .PARAMETER ThrowOnFailure + Throws an exception after the run if any tests failed. + .PARAMETER BinDir + Directory containing the built pwsh executable. Defaults to the current build output. + .PARAMETER powershell + Full path to the pwsh executable used for running tests. + .PARAMETER Pester + Path to the Pester module directory. + .PARAMETER Unelevate + Runs tests in an unelevated child process on Windows. + .PARAMETER Quiet + Suppresses most Pester output. + .PARAMETER Terse + Shows compact pass/fail indicators instead of full output lines. + .PARAMETER PassThru + Returns the Pester result object to the caller. + .PARAMETER Sudo + Runs tests under sudo on Unix (PassThru parameter set). + .PARAMETER IncludeFailingTest + Includes tests from tools/failingTests. + .PARAMETER IncludeCommonTests + Includes tests from test/common. + .PARAMETER ExperimentalFeatureName + Enables the named experimental feature for this test run via a temporary config file. + .PARAMETER Title + Title for the published test results. Defaults to 'PowerShell 7 Tests'. + .PARAMETER Wait + Waits for a debugger to attach before starting Pester (Debug builds only). + .PARAMETER SkipTestToolBuild + Skips rebuilding test tool executables before running tests. + .PARAMETER UseNuGetOrg + Switches NuGet config to public feeds before running tests. + #> [CmdletBinding(DefaultParameterSetName='default')] param( [Parameter(Position=0)] @@ -1428,7 +1749,7 @@ function Start-PSPester { if($IncludeCommonTests.IsPresent) { - $path = += "$PSScriptRoot/test/common" + $path += "$PSScriptRoot/test/common" } # we need to do few checks and if user didn't provide $ExcludeTag explicitly, we should alternate the default @@ -1744,6 +2065,20 @@ function Start-PSPester { function Publish-TestResults { + <# + .SYNOPSIS + Publishes test result files to Azure DevOps or GitHub Actions. + .DESCRIPTION + In an Azure DevOps build (TF_BUILD), uploads the result file via a ##vso command + and attaches it as a build artifact. In GitHub Actions, copies the file to the + testResults directory under $env:RUNNER_WORKSPACE. Does nothing outside of CI environments. + .PARAMETER Title + The run title shown in the CI testing tab. + .PARAMETER Path + Path to the NUnit or XUnit result file to publish. + .PARAMETER Type + The result file format: 'NUnit' (default) or 'XUnit'. + #> param( [Parameter(Mandatory)] [string] @@ -1804,6 +2139,17 @@ function Publish-TestResults function script:Start-UnelevatedProcess { + <# + .SYNOPSIS + Starts a process at an unelevated trust level on Windows. + .DESCRIPTION + Uses runas.exe /trustlevel:0x20000 to launch a process without elevation. + Only supported on Windows and non-arm64 architectures. + .PARAMETER process + The path to the executable to start. + .PARAMETER arguments + Arguments to pass to the executable. + #> param( [string]$process, [string[]]$arguments @@ -1814,7 +2160,7 @@ function script:Start-UnelevatedProcess throw "Start-UnelevatedProcess is currently not supported on non-Windows platforms" } - if (-not $environment.OSArchitecture -eq 'arm64') + if ($environment.OSArchitecture -eq 'arm64') { throw "Start-UnelevatedProcess is currently not supported on arm64 platforms" } @@ -1824,6 +2170,18 @@ function script:Start-UnelevatedProcess function Show-PSPesterError { + <# + .SYNOPSIS + Outputs a formatted error block for a single Pester test failure. + .DESCRIPTION + Accepts either an XmlElement from a NUnit result file or a PSCustomObject from + a Pester PassThru result, and writes a structured description/name/message/stack-trace + block to the log output. + .PARAMETER testFailure + An XML test-case element from a Pester NUnit result file (xml parameter set). + .PARAMETER testFailureObject + A Pester test-result PSCustomObject from a PassThru run (object parameter set). + #> [CmdletBinding(DefaultParameterSetName='xml')] param ( [Parameter(ParameterSetName='xml',Mandatory)] @@ -1866,6 +2224,18 @@ $stack_trace function Get-PesterFailureFileInfo { + <# + .SYNOPSIS + Parses a Pester stack-trace string and returns the source file path and line number. + .DESCRIPTION + Tries several common stack-trace formats produced by Pester 4 and Pester 5 (on + both Windows and Unix) and returns a hashtable with File and Line keys. + Returns $null values for both keys when no pattern matches. + .PARAMETER StackTraceString + The raw stack trace text from a Pester test failure. + .OUTPUTS + System.Collections.Hashtable. A hashtable with 'File' (string) and 'Line' (string). + #> [CmdletBinding()] param ( [Parameter(Mandatory)] @@ -1879,23 +2249,23 @@ function Get-PesterFailureFileInfo # "at , C:\path\to\file.ps1: line 123" # "at 1 | Should -Be 2, /path/to/file.ps1:123" (Pester 5) # "at 1 | Should -Be 2, C:\path\to\file.ps1:123" (Pester 5 Windows) - + $result = @{ File = $null Line = $null } - + if ([string]::IsNullOrWhiteSpace($StackTraceString)) { return $result } - + # Try pattern: "at line: 123 in " (Pester 4) if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { $result.Line = $matches[1] $result.File = $matches[2].Trim() return $result } - + # Try pattern: ", :123" (Pester 5 format) # This handles both Unix paths (/path/file.ps1:123) and Windows paths (C:\path\file.ps1:123) if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { @@ -1903,7 +2273,7 @@ function Get-PesterFailureFileInfo $result.Line = $matches[2] return $result } - + # Try pattern: "at :123" (without comma) # Handle both absolute Unix and Windows paths if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { @@ -1911,24 +2281,33 @@ function Get-PesterFailureFileInfo $result.Line = $matches[2] return $result } - + # Try pattern: ": line 123" if ($StackTraceString -match '((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):\s*line\s+(\d+)(?:\r|\n|$)') { $result.File = $matches[1].Trim() $result.Line = $matches[2] return $result } - + # Try to extract just the file path if no line number found if ($StackTraceString -match '(?:at\s+|in\s+)?((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1)') { $result.File = $matches[1].Trim() } - + return $result } function Test-XUnitTestResults { + <# + .SYNOPSIS + Validates an xUnit XML result file and throws if any tests failed. + .DESCRIPTION + Parses the specified xUnit result file, logs description, name, message, and + stack trace for each failed test, then throws an exception summarizing the count. + .PARAMETER TestResultsFile + Path to the xUnit XML result file to validate. + #> param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] @@ -1984,6 +2363,23 @@ function Test-XUnitTestResults # Throw if a test failed function Test-PSPesterResults { + <# + .SYNOPSIS + Validates Pester test results and throws if any tests failed. + .DESCRIPTION + In file mode, reads a NUnit XML result file and logs each failure before throwing. + In object mode, inspects a Pester PassThru result object. Optionally permits + empty result sets. + .PARAMETER TestResultsFile + Path to the NUnit XML result file. Defaults to 'pester-tests.xml'. + .PARAMETER TestArea + Label for the test area, used in error messages. Defaults to 'test/powershell'. + .PARAMETER ResultObject + A Pester PassThru result object to inspect instead of parsing a file. + .PARAMETER CanHaveNoResult + When specified with ResultObject, suppresses the 'NO TESTS RUN' exception for + zero-count results. + #> [CmdletBinding(DefaultParameterSetName='file')] param( [Parameter(ParameterSetName='file')] @@ -2059,6 +2455,20 @@ function Test-PSPesterResults } function Start-PSxUnit { + <# + .SYNOPSIS + Runs the xUnit tests for the PowerShell engine. + .DESCRIPTION + Executes 'dotnet test' in the test/xUnit directory against the built PowerShell + binaries. On Unix, copies native libraries and required dependencies into the test + output directory. Publishes results to CI when not in debug-logging mode. + .PARAMETER xUnitTestResultsFile + Path for the xUnit XML result file. Defaults to 'xUnitResults.xml'. + .PARAMETER DebugLogging + Enables detailed console test output instead of writing an XML result file. + .PARAMETER Filter + An xUnit filter expression to restrict which tests are run. + #> [CmdletBinding()]param( [string] $xUnitTestResultsFile = "xUnitResults.xml", [switch] $DebugLogging, @@ -2150,6 +2560,29 @@ function Start-PSxUnit { } function Install-Dotnet { + <# + .SYNOPSIS + Installs the .NET SDK using the official install script. + .DESCRIPTION + Downloads and runs dotnet-install.sh (Linux/macOS) or dotnet-install.ps1 (Windows) + to install the specified SDK version into the user-local dotnet installation directory. + .PARAMETER Channel + The release channel to install from when no explicit version is given. + .PARAMETER Version + The exact SDK version to install. Defaults to the version required by this repository. + .PARAMETER Quality + The quality level (e.g. 'GA', 'preview') used when installing by channel. + .PARAMETER RemovePreviousVersion + Attempts to uninstall previously installed dotnet packages before installing. + .PARAMETER NoSudo + Omits sudo from install commands, useful inside containers running as root. + .PARAMETER InstallDir + Custom installation directory for the .NET SDK. + .PARAMETER AzureFeed + Override URL for the Azure CDN feed used to download the SDK. + .PARAMETER FeedCredential + Credential token for accessing a private Azure feed. + #> [CmdletBinding()] param( [string]$Channel = $dotnetCLIChannel, @@ -2301,6 +2734,15 @@ function Install-Dotnet { } function Get-RedHatPackageManager { + <# + .SYNOPSIS + Returns the install command prefix for the available Red Hat-family package manager. + .DESCRIPTION + Detects whether yum, dnf, or tdnf is installed and returns the corresponding + install command string for use in bootstrapping scripts. + .OUTPUTS + System.String. A package-manager install command such as 'dnf install -y -q'. + #> if ($environment.IsCentOS -or (Get-Command -Name yum -CommandType Application -ErrorAction SilentlyContinue)) { "yum install -y -q" } elseif ($environment.IsFedora -or (Get-Command -Name dnf -CommandType Application -ErrorAction SilentlyContinue)) { @@ -2313,6 +2755,27 @@ function Get-RedHatPackageManager { } function Start-PSBootstrap { + <# + .SYNOPSIS + Installs build dependencies for PowerShell. + .DESCRIPTION + Depending on the selected scenario, installs native OS packages, the required + .NET SDK, Windows packaging tools (WiX), and/or .NET global tools (dotnet-format). + Supports Linux, macOS, and Windows. + .PARAMETER Channel + The .NET SDK release channel to use when installing by channel. + .PARAMETER Version + The exact .NET SDK version to install. Defaults to the required version. + .PARAMETER NoSudo + Omits sudo from native-package install commands, useful inside containers. + .PARAMETER BuildLinuxArm + Installs Linux ARM cross-compilation dependencies (Ubuntu/AzureLinux only). + .PARAMETER Force + Forces .NET SDK reinstallation even if the correct version is already present. + .PARAMETER Scenario + What to install: 'Package' (packaging tools), 'DotNet' (.NET SDK), + 'Both' (Package + DotNet), 'Tools' (.NET global tools), or 'All' (everything). + #> [CmdletBinding()] param( [string]$Channel = $dotnetCLIChannel, @@ -2580,6 +3043,17 @@ function Start-PSBootstrap { ## If the required SDK version is found, return it. ## Otherwise, return the latest installed SDK version that can be found. function Find-RequiredSDK { + <# + .SYNOPSIS + Returns the installed .NET SDK version that best satisfies the required version. + .DESCRIPTION + Lists installed SDKs with 'dotnet --list-sdks'. Returns the required version + string if it is installed; otherwise returns the newest installed SDK version. + .PARAMETER requiredSdkVersion + The exact .NET SDK version string to search for. + .OUTPUTS + System.String. The matched or newest installed SDK version string. + #> param( [Parameter(Mandatory, Position = 0)] [string] $requiredSdkVersion @@ -2604,6 +3078,28 @@ function Find-RequiredSDK { } function Start-DevPowerShell { + <# + .SYNOPSIS + Launches a PowerShell session using the locally built pwsh. + .DESCRIPTION + Starts a new pwsh process from the build output directory, optionally setting + the DEVPATH environment variable, redirecting PSModulePath to the built Modules + directory, and loading or suppressing the user profile. + .PARAMETER ArgumentList + Additional arguments passed to the pwsh process. + .PARAMETER LoadProfile + When specified, the user profile is loaded (by default -noprofile is prepended). + .PARAMETER Configuration + Build configuration whose output directory to use (ConfigurationParamSet). + .PARAMETER BinDir + Explicit path to the directory containing the pwsh binary (BinDirParamSet). + .PARAMETER NoNewWindow + Runs pwsh in the current console window instead of a new one. + .PARAMETER Command + A command string passed to pwsh via -command. + .PARAMETER KeepPSModulePath + Preserves the existing PSModulePath instead of redirecting it to the build output. + #> [CmdletBinding(DefaultParameterSetName='ConfigurationParamSet')] param( [string[]]$ArgumentList = @(), @@ -2671,6 +3167,16 @@ function Start-DevPowerShell { function Start-TypeGen { + <# + .SYNOPSIS + Generates the CorePsTypeCatalog type-catalog file. + .DESCRIPTION + Invokes the TypeCatalogGen .NET tool to produce CorePsTypeCatalog.cs, which maps + .NET types to their containing assemblies. The output .inc file name varies by + runtime to allow simultaneous builds on Windows and WSL. + .PARAMETER IncFileName + Name of the .inc file listing dependent assemblies. Defaults to 'powershell.inc'. + #> [CmdletBinding()] param ( @@ -2699,6 +3205,13 @@ function Start-TypeGen function Start-ResGen { + <# + .SYNOPSIS + Regenerates C# resource bindings from resx files. + .DESCRIPTION + Runs the ResGen .NET tool in src/ResGen to produce strongly-typed resource classes + for all resx files in the PowerShell project. + #> [CmdletBinding()] param() @@ -2771,6 +3284,17 @@ function Set-PSEnvironmentVariable { } function Find-Dotnet { + <# + .SYNOPSIS + Ensures the required .NET SDK is available on PATH. + .DESCRIPTION + Checks whether the dotnet currently on PATH can locate the required SDK version. + If not, prepends the user-local dotnet installation directory to PATH. + Optionally sets DOTNET_ROOT and adds the global tools directory to PATH. + .PARAMETER SetDotnetRoot + When specified, sets the DOTNET_ROOT environment variable and adds the + .NET global tools path to PATH. + #> param ( [switch] $SetDotnetRoot ) @@ -2867,6 +3391,14 @@ function Convert-TxtResourceToXml } function script:Use-MSBuild { + <# + .SYNOPSIS + Ensures that the msbuild command is available in the current scope. + .DESCRIPTION + If msbuild is not found in PATH, creates a script-scoped alias pointing to the + .NET Framework 4 MSBuild at its standard Windows location. Throws if neither + location provides a usable msbuild. + #> # TODO: we probably should require a particular version of msbuild, if we are taking this dependency # msbuild v14 and msbuild v4 behaviors are different for XAML generation $frameworkMsBuildLocation = "${env:SystemRoot}\Microsoft.Net\Framework\v4.0.30319\msbuild" @@ -2886,6 +3418,18 @@ function script:Use-MSBuild { function script:Write-Log { + <# + .SYNOPSIS + Writes a colored message to the host, with optional error annotation. + .DESCRIPTION + In GitHub Actions, error messages are emitted as workflow error annotations + using the '::error::' command. Normal messages are written in green; errors + in red. Console colors are reset after each call. + .PARAMETER message + The text to write. + .PARAMETER isError + When specified, writes the message as an error (red / GitHub Actions annotation). + #> param ( [Parameter(Position=0, Mandatory)] @@ -2913,6 +3457,18 @@ function script:Write-Log } function script:Write-LogGroup { + <# + .SYNOPSIS + Emits a titled group of log messages wrapped in log-group markers. + .DESCRIPTION + Calls Write-LogGroupStart, writes each message line via Write-Log, then calls + Write-LogGroupEnd. In GitHub Actions this creates a collapsible group; on other + hosts it adds BEGIN/END banners. + .PARAMETER Message + One or more message lines to write inside the group. + .PARAMETER Title + The title displayed for the log group. + #> param ( [Parameter(Position = 0, Mandatory)] @@ -2935,6 +3491,15 @@ function script:Write-LogGroup { $script:logGroupColor = [System.ConsoleColor]::Cyan function script:Write-LogGroupStart { + <# + .SYNOPSIS + Opens a collapsible log group section. + .DESCRIPTION + In GitHub Actions emits '::group::'. On other hosts writes a colored + begin banner using the script-level log group color. + .PARAMETER Title + The label for the group. + #> param ( [Parameter(Mandatory)] @@ -2950,6 +3515,15 @@ function script:Write-LogGroupStart { } function script:Write-LogGroupEnd { + <# + .SYNOPSIS + Closes a collapsible log group section. + .DESCRIPTION + In GitHub Actions emits '::endgroup::'. On other hosts writes a colored + end banner using the script-level log group color. + .PARAMETER Title + The group label (used only in non-GitHub-Actions output). + #> param ( [Parameter(Mandatory)] @@ -2965,6 +3539,20 @@ function script:Write-LogGroupEnd { } function script:precheck([string]$command, [string]$missedMessage) { + <# + .SYNOPSIS + Tests whether a command exists on PATH and optionally emits a warning if missing. + .DESCRIPTION + Uses Get-Command to locate the specified command. Returns $true if found, + $false otherwise. If the command is absent and a message is provided, + Write-Warning is called with that message. + .PARAMETER command + The command name to look for. + .PARAMETER missedMessage + Warning text to emit when the command is not found. Pass $null to suppress it. + .OUTPUTS + System.Boolean. $true when the command is found; $false otherwise. + #> $c = Get-Command $command -ErrorAction Ignore if (-not $c) { if (-not [string]::IsNullOrEmpty($missedMessage)) @@ -2980,6 +3568,13 @@ function script:precheck([string]$command, [string]$missedMessage) { # Cleans the PowerShell repo - everything but the root folder function Clear-PSRepo { + <# + .SYNOPSIS + Cleans all subdirectories of the PowerShell repository using 'git clean -fdX'. + .DESCRIPTION + Iterates over every top-level directory under the repository root and removes all + files that are not tracked by git, including ignored files. + #> [CmdletBinding()] param() @@ -2992,6 +3587,20 @@ function Clear-PSRepo # Install PowerShell modules such as PackageManagement, PowerShellGet function Copy-PSGalleryModules { + <# + .SYNOPSIS + Copies PowerShell Gallery modules from the NuGet cache to a Modules directory. + .DESCRIPTION + Reads the PackageReference items in the specified csproj file, resolves each + package from the NuGet global cache, and copies it to the destination directory. + Package nupkg and metadata files are excluded from the copy. + .PARAMETER CsProjPath + Path to the csproj file whose PackageReference items describe Gallery modules. + .PARAMETER Destination + Destination Modules directory. Must end with 'Modules'. + .PARAMETER Force + Forces NuGet package restore even if packages are already present. + #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] @@ -3051,6 +3660,22 @@ function Copy-PSGalleryModules function Merge-TestLogs { + <# + .SYNOPSIS + Merges xUnit and NUnit test log files into a single xUnit XML file. + .DESCRIPTION + Converts NUnit Pester logs to xUnit assembly format and appends them, along with + any additional xUnit logs, to the primary xUnit log. The merged result is saved + to the specified output path. + .PARAMETER XUnitLogPath + Path to the primary xUnit XML log file. + .PARAMETER NUnitLogPath + One or more NUnit (Pester) XML log file paths to merge in. + .PARAMETER AdditionalXUnitLogPath + Optional additional xUnit XML log files to append. + .PARAMETER OutputLogPath + Path for the merged xUnit output file. + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -3092,6 +3717,23 @@ function Merge-TestLogs } function ConvertFrom-PesterLog { + <# + .SYNOPSIS + Converts Pester NUnit XML log files to xUnit assembly format. + .DESCRIPTION + Accepts one or more NUnit log files produced by Pester, or existing xUnit logs, + and converts them to an in-memory xUnit assembly object model. If multiple logs + are provided and -MultipleLog is not set, they are combined into a single + assemblies object. + .PARAMETER Logfile + Path(s) to the NUnit or xUnit log file(s) to convert. Accepts pipeline input. + .PARAMETER IncludeEmpty + When specified, includes test assemblies that contain zero test cases. + .PARAMETER MultipleLog + When specified, returns one assemblies object per log file instead of combining. + .OUTPUTS + assemblies. One or more xUnit assemblies objects containing converted test data. + #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 0)] @@ -3099,21 +3741,6 @@ function ConvertFrom-PesterLog { [Parameter()][switch]$IncludeEmpty, [Parameter()][switch]$MultipleLog ) - <# -Convert our test logs to -xunit schema - top level assemblies -Pester conversion -foreach $r in "test-results"."test-suite".results."test-suite" -assembly - name = $r.Description - config-file = log file (this is the only way we can determine between admin/nonadmin log) - test-framework = Pester - environment = top-level "test-results.environment.platform - run-date = date (doesn't exist in pester except for beginning) - run-time = time - time = -#> - BEGIN { # CLASSES class assemblies { @@ -3460,6 +4087,17 @@ assembly # Save PSOptions to be restored by Restore-PSOptions function Save-PSOptions { + <# + .SYNOPSIS + Persists the current PSOptions to a JSON file. + .DESCRIPTION + Serializes the current build options (or the supplied Options object) to JSON + and writes them to the specified path. Defaults to psoptions.json in the repo root. + .PARAMETER PSOptionsPath + Path to the JSON file to write. Defaults to '$PSScriptRoot/psoptions.json'. + .PARAMETER Options + PSOptions object to save. Defaults to the current build options. + #> param( [ValidateScript({$parent = Split-Path $_;if($parent){Test-Path $parent}else{return $true}})] [ValidateNotNullOrEmpty()] @@ -3477,6 +4115,17 @@ function Save-PSOptions { # Restore PSOptions # Optionally remove the PSOptions file function Restore-PSOptions { + <# + .SYNOPSIS + Loads saved PSOptions from a JSON file and makes them the active build options. + .DESCRIPTION + Reads the JSON file produced by Save-PSOptions, reconstructs a PSOptions + hashtable, and stores it via Set-PSOptions. Optionally deletes the file afterward. + .PARAMETER PSOptionsPath + Path to the JSON file to read. Defaults to '$PSScriptRoot/psoptions.json'. + .PARAMETER Remove + When specified, deletes the JSON file after loading. + #> param( [ValidateScript({Test-Path $_})] [string] @@ -3509,6 +4158,31 @@ function Restore-PSOptions { function New-PSOptionsObject { + <# + .SYNOPSIS + Constructs the PSOptions hashtable from individual build-option components. + .DESCRIPTION + Assembles the hashtable consumed by Start-PSBuild, Restore-PSPackage, and related + commands. Prefer New-PSOptions, which auto-computes fields such as the output path. + .PARAMETER RootInfo + PSCustomObject with repo root path validation metadata. + .PARAMETER Top + Path to the top-level project directory (pwsh source directory). + .PARAMETER Runtime + The .NET runtime identifier (RID) for the build. + .PARAMETER Configuration + The build configuration: Debug, Release, CodeCoverage, or StaticAnalysis. + .PARAMETER PSModuleRestore + Whether Gallery modules should be restored to the build output. + .PARAMETER Framework + The target .NET framework moniker, e.g. 'net11.0'. + .PARAMETER Output + Full path to the output pwsh executable. + .PARAMETER ForMinimalSize + Whether this is a minimal-size build. + .OUTPUTS + System.Collections.Hashtable. A PSOptions hashtable. + #> param( [PSCustomObject] $RootInfo, @@ -3679,6 +4353,17 @@ $script:RESX_TEMPLATE = @' '@ function Get-UniquePackageFolderName { + <# + .SYNOPSIS + Returns a unique temporary folder path for a test package under the specified root. + .DESCRIPTION + Tries the path '<Root>/TestPackage' first, then appends a random numeric suffix + until an unused path is found. Throws if a unique name cannot be found in 10 tries. + .PARAMETER Root + The parent directory under which the unique folder name is generated. + .OUTPUTS + System.String. A path under Root that does not yet exist. + #> param( [Parameter(Mandatory)] $Root ) @@ -3705,6 +4390,18 @@ function Get-UniquePackageFolderName { function New-TestPackage { + <# + .SYNOPSIS + Creates a zip archive containing all test content and test tools. + .DESCRIPTION + Builds and publishes test tools, copies the test directory, assets directory, + and resx resource directories into a temporary staging folder, then zips the + staging folder to TestPackage.zip in the specified destination directory. + .PARAMETER Destination + Directory where the TestPackage.zip file is created. + .PARAMETER Runtime + The .NET runtime identifier (RID) used when publishing test tool executables. + #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -3781,6 +4478,16 @@ class NugetPackageSource { } function New-NugetPackageSource { + <# + .SYNOPSIS + Creates a NugetPackageSource object with the given URL and name. + .PARAMETER Url + The NuGet feed URL. + .PARAMETER Name + The feed name used as the key in nuget.config. + .OUTPUTS + NugetPackageSource. An object with Url and Name properties. + #> param( [Parameter(Mandatory = $true)] [string]$Url, [Parameter(Mandatory = $true)] [string] $Name @@ -3791,6 +4498,22 @@ function New-NugetPackageSource { $script:NuGetEndpointCredentials = [System.Collections.Generic.Dictionary[String,System.Object]]::new() function New-NugetConfigFile { + <# + .SYNOPSIS + Generates a nuget.config file at the specified destination. + .DESCRIPTION + Creates a nuget.config XML file with the supplied package sources and optional + credentials. The generated file is marked as skip-worktree in git to prevent + accidental commits of feed credentials. + .PARAMETER NugetPackageSource + One or more NugetPackageSource objects defining the feeds to include. + .PARAMETER Destination + Directory where nuget.config is written. + .PARAMETER UserName + Username for authenticated feed access. + .PARAMETER ClearTextPAT + Personal access token in clear text for authenticated feed access. + #> param( [Parameter(Mandatory = $true, ParameterSetName ='user')] [Parameter(Mandatory = $true, ParameterSetName ='nouser')] @@ -3872,10 +4595,24 @@ function New-NugetConfigFile { } function Clear-PipelineNugetAuthentication { + <# + .SYNOPSIS + Clears cached NuGet feed credentials used by the pipeline. + .DESCRIPTION + Removes all entries from the script-scoped NuGetEndpointCredentials dictionary. + #> $script:NuGetEndpointCredentials.Clear() } function Set-PipelineNugetAuthentication { + <# + .SYNOPSIS + Publishes cached NuGet feed credentials to the Azure DevOps pipeline. + .DESCRIPTION + Serializes the script-scoped NuGetEndpointCredentials dictionary to JSON and sets + the VSS_NUGET_EXTERNAL_FEED_ENDPOINTS pipeline variable so that subsequent NuGet + operations authenticate automatically. + #> $endpointcredentials = @() foreach ($key in $script:NuGetEndpointCredentials.Keys) { @@ -3890,6 +4627,14 @@ function Set-PipelineNugetAuthentication { function Set-CorrectLocale { + <# + .SYNOPSIS + Configures the Linux locale to en_US.UTF-8 for consistent build behavior. + .DESCRIPTION + On Ubuntu 20+ systems, generates the en_US.UTF-8 locale and sets LC_ALL and LANG + environment variables. Skips execution on non-Linux platforms and Ubuntu versions + earlier than 20. + #> Write-LogGroupStart -Title "Set-CorrectLocale" if (-not $IsLinux) @@ -3926,6 +4671,13 @@ function Set-CorrectLocale } function Write-Locale { + <# + .SYNOPSIS + Writes the current system locale settings to the log output. + .DESCRIPTION + Runs the 'locale' command on Linux or macOS and writes the output inside a + collapsible log group. Does nothing on Windows. + #> if (-not $IsLinux -and -not $IsMacOS) { Write-Verbose -Message "only supported on Linux and macOS" -Verbose return @@ -3937,6 +4689,13 @@ function Write-Locale { } function Install-AzCopy { + <# + .SYNOPSIS + Downloads and installs AzCopy v10 on Windows. + .DESCRIPTION + Downloads the AzCopy v10 zip archive from the official Microsoft URL and extracts + it to the Agent tools directory. Skips installation if AzCopy is already present. + #> $testPath = "C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy\AzCopy.exe" if (Test-Path $testPath) { Write-Verbose "AzCopy already installed" -Verbose @@ -3951,6 +4710,15 @@ function Install-AzCopy { } function Find-AzCopy { + <# + .SYNOPSIS + Locates the AzCopy executable on the system. + .DESCRIPTION + Searches several well-known installation paths for AzCopy.exe and falls back to + Get-Command if none of the paths contain the executable. + .OUTPUTS + System.String. The full path to the AzCopy executable. + #> $searchPaths = @('$(Agent.ToolsDirectory)\azcopy10\AzCopy.exe', "C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy\AzCopy.exe", "C:\azcopy10\AzCopy.exe") foreach ($filter in $searchPaths) { @@ -3966,6 +4734,16 @@ function Find-AzCopy { function Clear-NativeDependencies { + <# + .SYNOPSIS + Removes unnecessary native dependency files from the publish output. + .DESCRIPTION + Strips architecture-specific DiaSym reader DLLs that are not needed for the + target runtime from both the publish folder and the pwsh.deps.json manifest. + Skips fxdependent runtimes where no cleanup is needed. + .PARAMETER PublishFolder + Path to the publish output directory containing pwsh.deps.json. + #> param( [Parameter(Mandatory=$true)] [string] $PublishFolder ) @@ -4032,6 +4810,14 @@ function Clear-NativeDependencies function Update-DotNetSdkVersion { +<# + .SYNOPSIS + Updates the .NET SDK version in global.json and DotnetRuntimeMetadata.json. + .DESCRIPTION + Queries the official .NET SDK feed for the latest version in the current channel + and writes the new version to global.json and DotnetRuntimeMetadata.json. + #> + param() $globalJsonPath = "$PSScriptRoot/global.json" $globalJson = get-content $globalJsonPath | convertfrom-json $oldVersion = $globalJson.sdk.version @@ -4051,6 +4837,17 @@ function Update-DotNetSdkVersion { } function Set-PipelineVariable { + <# + .SYNOPSIS + Sets an Azure DevOps pipeline variable and the corresponding environment variable. + .DESCRIPTION + Emits a ##vso[task.setvariable] logging command so that subsequent pipeline steps + can access the variable, and also sets it in the current process environment. + .PARAMETER Name + The pipeline variable name. + .PARAMETER Value + The value to assign. + #> param( [parameter(Mandatory)] [string] $Name, From 1b390e1fa752d47c3073e9f0d513d0a13416212f Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:22:31 -0400 Subject: [PATCH 239/255] Update package references and move to .NET SDK 11.0-preview.2 (#27117) --- global.json | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 6 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 15 ++- .../Microsoft.WSMan.Management.csproj | 2 +- .../PSVersionInfoGenerator.csproj | 4 +- .../System.Management.Automation.csproj | 12 +- .../ResultsComparer/ResultsComparer.csproj | 2 +- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- tools/cgmanifest/main/cgmanifest.json | 108 ++++++++++-------- 13 files changed, 85 insertions(+), 76 deletions(-) diff --git a/global.json b/global.json index 2fe6c88b1f6..ef31c0723ff 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "11.0.100-preview.1.26104.118" + "version": "11.0.100-preview.2.26159.112" } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index ab9bd210353..8e5a6b3f2e3 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ <ItemGroup> <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> - <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="11.0.0-preview.2.26159.112" /> </ItemGroup> <PropertyGroup> diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index a8c2c34aca2..8bb897e0095 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.2.26159.112" /> </ItemGroup> </Project> diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index b2fae8b21e7..d6cd95677ab 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -8,7 +8,7 @@ <ItemGroup> <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> - <PackageReference Include="Markdig.Signed" Version="1.0.0" /> + <PackageReference Include="Markdig.Signed" Version="1.1.2" /> <PackageReference Include="Microsoft.PowerShell.MarkdownRender" Version="7.2.1" /> </ItemGroup> @@ -32,8 +32,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" /> - <PackageReference Include="System.Drawing.Common" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" /> + <PackageReference Include="System.Drawing.Common" Version="11.0.0-preview.2.26159.112" /> <PackageReference Include="JsonSchema.Net" Version="7.4.0" /> </ItemGroup> diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 6df470621f5..1a63891ef77 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.Diagnostics.EventLog" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="System.Diagnostics.EventLog" Version="11.0.0-preview.2.26159.112" /> </ItemGroup> </Project> diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 979764ff79f..ecc224713cc 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,19 +16,19 @@ <ItemGroup> <!-- This section is to force the version of non-direct dependencies --> - <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="11.0.0-preview.2.26159.112" /> <!-- the following package(s) are from https://github.com/dotnet/fxdac --> <PackageReference Include="System.Data.SqlClient" Version="4.9.1" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.IO.Packaging" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="System.Net.Http.WinHttpHandler" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="System.IO.Packaging" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Net.Http.WinHttpHandler" Version="11.0.0-preview.2.26159.112" /> <!-- the following package(s) are from https://github.com/dotnet/wcf --> <PackageReference Include="System.ServiceModel.Http" Version="10.0.652802" /> <PackageReference Include="System.ServiceModel.NetTcp" Version="10.0.652802" /> <PackageReference Include="System.ServiceModel.Primitives" Version="10.0.652802" /> <!-- the source could not be found for the following package(s) --> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.2.26159.112" /> </ItemGroup> <!-- @@ -41,14 +41,13 @@ dotnet msbuild ./dummy.csproj /t:ResolveAssemblyReferencesDesignTime /fileLogger /noconsolelogger /v:diag 3. Search '_ReferencesFromRAR' in the produced 'msbuild.log' file. --> - <Target Name="_GetDependencies" - DependsOnTargets="ResolveAssemblyReferencesDesignTime"> + <Target Name="_GetDependencies" DependsOnTargets="ResolveAssemblyReferencesDesignTime"> <ItemGroup> <!-- Excludes 'Microsoft.Management.Infrastructure' from the type catalog reference list, as it is provided separately at runtime and must not be included in the generated catalog. --> - <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' "/> + <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' " /> </ItemGroup> <WriteLinesToFile File="$(_DependencyFile)" Lines="@(_RefAssemblyPath)" Overwrite="true" /> </Target> diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index e3bb66c87ac..bc6e4b105de 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> <ProjectReference Include="..\Microsoft.WSMan.Runtime\Microsoft.WSMan.Runtime.csproj" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.2.26159.112" /> </ItemGroup> <PropertyGroup> diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj index 44d93a5e31b..6ba483b5f6e 100644 --- a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj +++ b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj @@ -14,7 +14,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" PrivateAssets="all" /> - <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" PrivateAssets="all" /> </ItemGroup> </Project> diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index d8a66b2ab74..fa797a7f0de 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,12 +32,12 @@ <!-- the Application Insights package --> <PackageReference Include="Microsoft.ApplicationInsights" Version="2.23.0" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="System.Configuration.ConfigurationManager" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="System.DirectoryServices" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="System.Management" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="System.Security.Cryptography.Pkcs" Version="11.0.0-preview.1.26104.118" /> - <PackageReference Include="System.Security.Permissions" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Configuration.ConfigurationManager" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.DirectoryServices" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Management" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Security.Cryptography.Pkcs" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Security.Permissions" Version="11.0.0-preview.2.26159.112" /> <!-- the following package(s) are from the powershell org --> <PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" /> <PackageReference Include="Microsoft.PowerShell.Native" Version="700.0.0-rc.1" /> diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index 58ea02cf1b5..ff3f14d5a0d 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -10,6 +10,6 @@ <PackageReference Include="MarkdownLog.NS20" Version="0.10.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="BenchmarkDotNet" Version="0.15.8" /> - <PackageReference Include="Perfolizer" Version="0.6.7" /> + <PackageReference Include="Perfolizer" Version="0.7.1" /> </ItemGroup> </Project> diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index 7580229ce98..906820d262b 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.2.26159.112" /> <PackageReference Include="System.Data.SqlClient" Version="4.9.1" /> </ItemGroup> diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index 00cd6825dff..c632e960a53 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="11.0.0-preview.1.26104.118" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="11.0.0-preview.2.26159.112" /> </ItemGroup> </Project> diff --git a/tools/cgmanifest/main/cgmanifest.json b/tools/cgmanifest/main/cgmanifest.json index 4d856723eaa..5322bc102bd 100644 --- a/tools/cgmanifest/main/cgmanifest.json +++ b/tools/cgmanifest/main/cgmanifest.json @@ -66,7 +66,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "1.0.0" + "Version": "1.1.2" } }, "DevelopmentDependency": false @@ -86,7 +86,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -126,7 +126,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -166,7 +166,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -176,7 +176,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -186,7 +186,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -206,7 +206,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -216,7 +216,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -226,7 +226,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -236,7 +236,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -246,7 +246,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -256,7 +256,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -266,7 +266,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -276,7 +276,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -286,7 +286,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -296,7 +296,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -306,7 +306,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -316,7 +316,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -326,7 +326,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -336,7 +336,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -356,7 +356,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -366,7 +366,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -376,7 +376,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -436,7 +436,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -446,7 +446,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -456,7 +456,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -466,7 +466,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -476,7 +476,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -486,7 +486,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -506,7 +506,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -516,7 +516,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -526,7 +526,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -536,7 +536,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -546,7 +546,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -556,7 +556,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -566,7 +566,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -576,7 +576,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -586,7 +586,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -596,7 +596,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -606,7 +606,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -616,7 +616,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -626,7 +626,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -636,7 +636,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -646,7 +646,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -656,7 +656,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -706,7 +706,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -716,7 +716,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -726,7 +726,17 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "10.0.3" + "Version": "10.0.5" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Text.Encoding.CodePages", + "Version": "10.0.5" } }, "DevelopmentDependency": false @@ -746,7 +756,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "10.0.3" + "Version": "10.0.5" } }, "DevelopmentDependency": false From ff37ab4539966407a14d89c66004fd5f479836db Mon Sep 17 00:00:00 2001 From: Dongbo Wang <dongbow@microsoft.com> Date: Tue, 31 Mar 2026 14:24:41 -0700 Subject: [PATCH 240/255] Revert "Fetch latest ICU release version dynamically" (#27127) --- tools/packaging/packaging.psm1 | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index d420ceb620d..198fb91912e 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -2152,7 +2152,7 @@ function Get-PackageDependencies # than the build version and we know that older versions just works. # $MinICUVersion = 60 # runtime minimum supported - $BuildICUVersion = Get-IcuLatestRelease + $BuildICUVersion = 76 # current build version $MaxICUVersion = $BuildICUVersion + 30 # headroom if ($Distribution -eq 'deb') { @@ -5809,15 +5809,3 @@ function Test-IsProductFile { return $false } - -# Get major version from latest ICU release (latest: stable version) -function Get-IcuLatestRelease { - $response = Invoke-WebRequest -Uri "https://github.com/unicode-org/icu/releases/latest" - $tagUrl = ($response.Links | Where-Object href -like "*releases/tag/release-*")[0].href - - if ($tagUrl -match 'release-(\d+)\.') { - return [int]$Matches[1] - } - - throw "Unable to determine the latest ICU release version." -} From 45a80d9a4894802a61637cc7c8843bc66b0bcc5d Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:20:45 -0500 Subject: [PATCH 241/255] Separate Store Package Creation, Skip Polling for Store Publish, Clean up PDP-Media (#27024) Co-authored-by: Justin Chung <chungjustin@microsoft.com> --- ...Shell-Coordinated_Packages-NonOfficial.yml | 4 +- .../PowerShell-Packages-NonOfficial.yml | 4 +- .../PowerShell-Release-Azure-NonOfficial.yml | 2 +- .../PowerShell-Release-NonOfficial.yml | 4 +- .../PowerShell-vPack-NonOfficial.yml | 4 +- .../store/PDP/PDP-Media/en-US/Error.png | Bin 120539 -> 0 bytes .../PDP-Media/en-US/Experimental_Features.png | Bin 161370 -> 0 bytes .../PDP/PDP-Media/en-US/Feedback_Provider.png | Bin 167038 -> 0 bytes .../PDP/PDP-Media/en-US/Predictor_Inline.png | Bin 110258 -> 0 bytes .../PDP-Media/en-US/Predictor_ListView.png | Bin 146469 -> 0 bytes .../store/PDP/PDP-Media/en-US/Prompt.png | Bin 132747 -> 0 bytes .../PDP/PDP-Media/en-US/Stable_Release.png | Bin 179123 -> 0 bytes .../store/PDP/PDP-Media/en-US/pwshLogo.png | Bin 13152 -> 0 bytes .pipelines/store/PDP/PDP/en-US/PDP.xml | 25 -- .pipelines/templates/package-create-msix.yml | 197 -------------- .../templates/package-store-package.yml | 242 ++++++++++++++++++ .pipelines/templates/release-MSIX-Publish.yml | 14 +- .../stages/PowerShell-Packages-Stages.yml | 6 + 18 files changed, 266 insertions(+), 236 deletions(-) delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Error.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Experimental_Features.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Feedback_Provider.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Predictor_Inline.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Predictor_ListView.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Prompt.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/Stable_Release.png delete mode 100644 .pipelines/store/PDP/PDP-Media/en-US/pwshLogo.png create mode 100644 .pipelines/templates/package-store-package.yml diff --git a/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml index 55d4c4557d8..0b417df5c05 100644 --- a/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml +++ b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml @@ -45,7 +45,7 @@ resources: ref: refs/heads/main variables: - - template: ../templates/variables/PowerShell-Coordinated_Packages-Variables.yml + - template: ./pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml@self parameters: InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} ReleaseTagVar: ${{ parameters.ReleaseTagVar }} @@ -90,7 +90,7 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - template: ../templates/stages/PowerShell-Coordinated_Packages-Stages.yml + - template: ./pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml@self parameters: RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} diff --git a/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml index 81f343a04a0..9419d3f29b5 100644 --- a/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml +++ b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml @@ -31,7 +31,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time name: pkgs-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) variables: - - template: ../templates/variables/PowerShell-Packages-Variables.yml + - template: ./pipelines/templates/variables/PowerShell-Packages-Variables.yml@self parameters: debug: ${{ parameters.debug }} ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} @@ -92,6 +92,6 @@ extends: enabled: false tsaOptionsFile: .config\tsaoptions.json stages: - - template: ../templates/stages/PowerShell-Packages-Stages.yml + - template: ./pipelines/templates/stages/PowerShell-Packages-Stages.yml@self parameters: OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml index 681babb2220..b524cb0ff81 100644 --- a/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml +++ b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml @@ -17,7 +17,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time name: ev2-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) variables: - - template: ../templates/variables/PowerShell-Release-Azure-Variables.yml + - template: ./pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml@self parameters: debug: ${{ parameters.debug }} diff --git a/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml index ca5a6383f33..7864513fc2c 100644 --- a/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml +++ b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml @@ -33,7 +33,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time name: release-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) variables: - - template: ../templates/variables/PowerShell-Release-Variables.yml + - template: ./pipelines/templates/variables/PowerShell-Release-Variables.yml@self parameters: debug: ${{ parameters.debug }} ReleaseTagVar: ${{ parameters.ReleaseTagVar }} @@ -98,7 +98,7 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - template: ../templates/stages/PowerShell-Release-Stages.yml + - template: ./pipelines/templates/stages/PowerShell-Release-Stages.yml@self parameters: releaseEnvironment: Test SkipPublish: ${{ parameters.SkipPublish }} diff --git a/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml index 642b169adaf..f1f4211ca8f 100644 --- a/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml +++ b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml @@ -33,7 +33,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time name: vPack_$(Build.SourceBranchName)_NonOfficial_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) variables: - - template: ../templates/variables/PowerShell-vPack-Variables.yml + - template: ./pipelines/templates/variables/PowerShell-vPack-Variables.yml@self parameters: debug: ${{ parameters.debug }} ReleaseTagVar: ${{ parameters.ReleaseTagVar }} @@ -82,7 +82,7 @@ extends: enabled: false tsaOptionsFile: .config/tsaoptions.json stages: - - template: ../templates/stages/PowerShell-vPack-Stages.yml + - template: ./pipelines/templates/stages/PowerShell-vPack-Stages.yml@self parameters: createVPack: ${{ parameters.createVPack }} vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Error.png b/.pipelines/store/PDP/PDP-Media/en-US/Error.png deleted file mode 100644 index 48e96378055b5d7fbb4d744007c1ebc073da2453..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120539 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 diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Experimental_Features.png b/.pipelines/store/PDP/PDP-Media/en-US/Experimental_Features.png deleted file mode 100644 index 90420254a8e4a4720601e3050567db719819efc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161370 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 QBJkg#{kr>7G|bQcAJc$pZvX%Q diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Feedback_Provider.png b/.pipelines/store/PDP/PDP-Media/en-US/Feedback_Provider.png deleted file mode 100644 index f4084360d5c630119e469b721dde87920d5a8346..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167038 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 L$C>(5*M9y#()S`v diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Predictor_Inline.png b/.pipelines/store/PDP/PDP-Media/en-US/Predictor_Inline.png deleted file mode 100644 index 3b8d62284859c6ce60c96d643b3b2bdb994471d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110258 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 W56C)9ct$q>0000<MNUMnLSTZ6K}~c3 diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Predictor_ListView.png b/.pipelines/store/PDP/PDP-Media/en-US/Predictor_ListView.png deleted file mode 100644 index 1fb9a6247c576275add6641350ea1d667c2f005c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146469 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৉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 Y-W{2D?d+Of_f@>lUHq~3hwBgi2Mp#?lK=n! diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Prompt.png b/.pipelines/store/PDP/PDP-Media/en-US/Prompt.png deleted file mode 100644 index a40d6fddfdc44c9f7fb643ebc4e84eda527a395d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132747 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ǟ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 E0osNO-T(jq diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Stable_Release.png b/.pipelines/store/PDP/PDP-Media/en-US/Stable_Release.png deleted file mode 100644 index 2761a46a64ff6d281aeee9e9422c8a3a36ae814e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179123 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િI>b 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�E+=*#$Yf8)8vJb1}=9_dWABXCqD%pWa_q_sbD7Q@=YUrk0Qi3$a5 zS=Fn5S&oE`_j|oFZ_D+yPvE4}!D9m%`DxdI?vkAD0akeTcH_tF-D0v#6b<JE&NfK_ zTX%VLOs-hrhlr%QGRt0|)lr|OVGT_IxYgG{XkMeeXlgu1`0PG<>qA-#hksR^_wjm7 zyHWIrZM^A4S~+x3trRbH2sw_R%>kr3D@}`Ad<fwZBJQNp#*9s)AHw-+6Su3Zw2m;Q ztX9--vQGkF$=#1!a>QHe^23CtVu4>Z?VW2J{3z)kpX3whDnT;pQ~(JQt=qYXB@J*E z`j)AW*?o(cD5iT<t|Cxs$uDpC0Q9|{P7$4LAoete%eSS6F5OI9c5SqhNTRh_qxhZp zE9L{JcV-FLA8=JN7I<Ae!3im?%4ORH<WoaU_g~W{!#|e^C@Jh*5m~F!2&YJjL!01t z#KlcO@dNW7y+piqs)I+7;Urt;$*`G>yBSl?%1T*pj(Mhn8j{Dn581t?JSt0V5nBup zr#y!T7rD<{n%b)KmcUc)W!X;ML^VqfRTr`;J7%D^sWcAewPS3<tX8X$T%$~xQ1KLh zo6sfW(Rq*q==-rD@s<}%PG7t!NQe~{=kJ5Eiit(=sPh@sUD0I4Xpau%H0k-tOki_@ z>Ce~Ry+HK#%)_m*%Bm%7Bv(isWuwV62RP1+#>ej!2Qj+T=BZoXm#6Wy3UVW{q2*z< z@rS&i9x~qb_2{|suO76K^2N~rF(LZ$2rNs7eOSh<ap%VjfC`dgQkH=2dI5>LQTmIn zs}!8X)6LuL!GWy)qr&XsXculW*zV%^Ry5-MOb((ryxiJ%Cx`H(sK&F;m6Q~7!O!vC z`|u^VfW1L8oov2Rb`wihRWZ)9!bo!BorY5!dP~IeN!4lP8VE}?c-#Y_F|1E1rkJO> zBWVLXZL-bA1f}|`HzG2gWIPxB5rfkv1m8VAtDcb_5=R!u@k{#%ig#pGw$cHCR+Hr5 zd1G%;zQVt#Y=sQ=)Usc9PfI}KRWvm<#wnKnqi>}xq9`DTU~DRG$XyPwkeG`2E<^~9 z1&<Yl@;SvESph0}VyFNEI!~2Ii>q0EFWm84d0}$Qm)aI=vo5>J3`D#EewALs?q%>$ z@M^VD&z-q@4bALulo1vxhQoh~v4TRv=2;hSa6}{Y1|+KPk77;>mo;O=uHxRMw>eAk z7vDV{S2n#u$!h_Sb{UOcO%x1)s1_JLQBdAdGv4_Qi*{X;DY~bykvu!-m0l2MO}oFY zoj?y88S9M<OQAU611o0;?ug0^7CWxY+3|7<?;{AOPnf{wsih(l0ah8F=?@5`y3n5O zsJmEbk(ppOc_B}v%psuW&rkNnpD)nIZ=6%e{wIU(t}0^KqHE#{dBwq78Syz!7b4c3 zwVNLfw@!$*{W`F{E|W*dFA<VE!~M|45Ru4QSjr49k7>%`oTX3Pt<KN!=oq;;=IU<8 z{rqx=7u{D7W-xuQ-Q}Gc^D0+JA1#j0Q0k`(ClEMV2`(aRZ8wYc_JzCem`DDlOp$B< z-I+r<`sndAhxF!9`Ln%iBM~e|Ck3?s0k3TBc<n&WT8QJMMtBc+?7e^>uJkvjXnXVh z@t49=W46kmh%(uFe23)?AE)?l>z^8!+%2xlBN?%1#N)wfUGsT*XQ2$?C<r~xAQ>Hs z7l_NKoC9Hdf!j0clfh+YVU3pj_<^e<i#s)@aQ)Eq8~ZB9-{9g0j9(B_>6zgUYthHO z@OLWR-Dj4~rz<31GS4IxH>WyWR-MYLi=30VJEB({YJI$KH44~;$Wc3OGGHbm^%0** ziQLKnvC7vZer-+o4bB=FdCD;%0&*Vb>$y@(usr;yEc18C&R7P2#j2hLbxm!ftxlYG zL57dNB2tJlBHmbj4EQLC8|5bYBjlI!K$YGXzdv13h@wQLr^?tUlMl|vf3IO&>>fp} z<)tCo_(&c+s?x`_Au7Wp^*XS3644_m�-ID=%B|=h~b5ZOc}~$w;f(<4)0Z?9))3 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 diff --git a/.pipelines/store/PDP/PDP-Media/en-US/pwshLogo.png b/.pipelines/store/PDP/PDP-Media/en-US/pwshLogo.png deleted file mode 100644 index c531f719c85429fa6192eae82bb434e0318de801..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13152 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 diff --git a/.pipelines/store/PDP/PDP/en-US/PDP.xml b/.pipelines/store/PDP/PDP/en-US/PDP.xml index 15d0bdf5270..ce36a3677f7 100644 --- a/.pipelines/store/PDP/PDP/en-US/PDP.xml +++ b/.pipelines/store/PDP/PDP/en-US/PDP.xml @@ -55,33 +55,8 @@ PowerShell is Open Source. See https://github.com/powershell/powershell </Descr <ScreenshotCaptions> <!-- Valid length: 200 character limit, up to 9 elements per platform --> <!-- Valid attributes: any of DesktopImage, MobileImage, XboxImage, SurfaceHubImage, and HoloLensImage --> - <Caption DesktopImage="Prompt.png" _locID="App_caption1"> - <!-- _locComment_text="{MaxLength=200} Screenshot caption 1" --> - Prompt - </Caption> - <Caption DesktopImage="Predictor_Inline.png" _locID="App_caption2"> - <!-- _locComment_text="{MaxLength=200} Screenshot caption 2" --> - Inline Prediction - </Caption> - <Caption DesktopImage="Predictor_ListView.png" _locID="App_caption3"> - <!-- _locComment_text="{MaxLength=200} Screenshot caption 3" --> - Prediction List View - </Caption> - <Caption DesktopImage="Error.png" _locID="App_caption4"> - <!-- _locComment_text="{MaxLength=200} Screenshot caption 4" --> - Error Feedback Provider - </Caption> - <Caption DesktopImage="Feedback_Provider.png" _locID="App_caption5"> - <!-- _locComment_text="{MaxLength=200} Screenshot caption 5" --> - Feedback Provider - </Caption> - <Caption DesktopImage="Experimental_Features.png" _locID="App_caption6"> - <!-- _locComment_text="{MaxLength=200} Screenshot caption 6" --> - Experimental Features - </Caption> </ScreenshotCaptions> <AdditionalAssets> - <Icon FileName="pwshLogo.png" /> <!-- Valid elements:--> <!-- HeroImage414x180, HeroImage846x468, HeroImage558x756, HeroImage414x468, HeroImage558x558, HeroImage2400x1200,--> <!-- ScreenshotWXGA, ScreenshotHD720, ScreenshotWVGA, Doublewide, Panoramic, Square,--> diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index e461bb6efd9..97d2f4fc46a 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -12,7 +12,6 @@ jobs: variables: - group: msixTools - group: 'Azure Blob variable group' - - group: 'Store Publish Variables' - name: ob_sdl_credscan_suppressionsFile value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - name: ob_sdl_tsa_configFile @@ -153,199 +152,3 @@ jobs: Write-Verbose -Verbose "Uploaded Bundles:" Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose displayName: Upload msixbundle to Artifacts - - - pwsh: | - Write-Verbose -Verbose "Pipeline.Workspace: $(Pipeline.Workspace)" - Get-ChildItem -Path $(Pipeline.Workspace) -Recurse | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "System.DefaultWorkingDirectory: $(System.DefaultWorkingDirectory)" - Get-ChildItem -Path $(System.DefaultWorkingDirectory) -Recurse | Select-Object -ExpandProperty FullName - Test-Path -Path '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP-Private.xml' | Write-Verbose -Verbose - displayName: Output Pipeline.Workspace and System.DefaultWorkingDirectory - - - template: channelSelection.yml@self - - - pwsh: | - $IsLTS = '$(ChannelSelection.IsLTS)' -eq 'true' - $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' - $IsPreview = '$(ChannelSelection.IsPreview)' -eq 'true' - - Write-Verbose -Verbose "Channel Selection - LTS: $IsLTS, Stable: $IsStable, Preview: $IsPreview" - - # Define app configurations for each channel - $channelConfigs = @{ - 'LTS' = @{ - AppStoreName = 'PowerShell-LTS' - ProductId = '$(productId-LTS)' - AppId = '$(AppID-LTS)' - ServiceEndpoint = "StoreAppPublish-Stable" - } - 'Stable' = @{ - AppStoreName = 'PowerShell' - ProductId = '$(productId-Stable)' - AppId = '$(AppID-Stable)' - ServiceEndpoint = "StoreAppPublish-Stable" - } - 'Preview' = @{ - AppStoreName = 'PowerShell (Preview)' - ProductId = '$(productId-Preview)' - AppId = '$(AppID-Preview)' - ServiceEndpoint = "StoreAppPublish-Preview" - } - } - - $currentChannel = if ($IsLTS) { 'LTS' } - elseif ($IsStable) { 'Stable' } - elseif ($IsPreview) { 'Preview' } - else { - Write-Error "No valid channel detected" - exit 1 - } - - $config = $channelConfigs[$currentChannel] - Write-Verbose -Verbose "Selected channel: $currentChannel" - Write-Verbose -Verbose "App Store Name: $($config.AppStoreName)" - Write-Verbose -Verbose "Product ID: $($config.ProductId)" - - # Update PDP.xml file - $pdpPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP/en-US/PDP.xml' - if (Test-Path $pdpPath) { - Write-Verbose -Verbose "Updating PDP file: $pdpPath" - - [xml]$pdpXml = Get-Content $pdpPath -Raw - - # Create namespace manager for XML with default namespace - $nsManager = New-Object System.Xml.XmlNamespaceManager($pdpXml.NameTable) - $nsManager.AddNamespace("pd", "http://schemas.microsoft.com/appx/2012/ProductDescription") - - $appStoreNameElement = $pdpXml.SelectSingleNode("//pd:AppStoreName", $nsManager) - if ($appStoreNameElement) { - $appStoreNameElement.SetAttribute("_locID", $config.AppStoreName) - Write-Verbose -Verbose "Updated AppStoreName _locID to: $($config.AppStoreName)" - } else { - Write-Warning "AppStoreName element not found in PDP file" - } - - $pdpXml.Save($pdpPath) - Write-Verbose -Verbose "PDP file updated successfully" - Get-Content -Path $pdpPath | Write-Verbose -Verbose - } else { - Write-Error "PDP file not found: $pdpPath" - exit 1 - } - - # Update SBConfig.json file - $sbConfigPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/SBConfig.json' - if (Test-Path $sbConfigPath) { - Write-Verbose -Verbose "Updating SBConfig file: $sbConfigPath" - - $sbConfigJson = Get-Content $sbConfigPath -Raw | ConvertFrom-Json - - $sbConfigJson.appSubmission.productId = $config.ProductId - Write-Verbose -Verbose "Updated productId to: $($config.ProductId)" - - $sbConfigJson | ConvertTo-Json -Depth 100 | Set-Content $sbConfigPath -Encoding UTF8 - Write-Verbose -Verbose "SBConfig file updated successfully" - Get-Content -Path $sbConfigPath | Write-Verbose -Verbose - } else { - Write-Error "SBConfig file not found: $sbConfigPath" - exit 1 - } - - Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" - Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" - - # Select the correct bundle based on channel - $bundleFiles = @(Get-ChildItem -Path '$(BundleDir)' -Filter '*.msixbundle') - Write-Verbose -Verbose "Available bundles: $($bundleFiles.Name -join ', ')" - - if ($IsLTS) { - $bundleFile = $bundleFiles | Where-Object { $_.Name -match '-LTS-' } - } else { - # Catches Stable or Preview - $bundleFile = $bundleFiles | Where-Object { $_.Name -notmatch '-LTS-' } - } - - if (-not $bundleFile) { - Write-Error "No matching bundle found for channel '$currentChannel'. Available bundles: $($bundleFiles.Name -join ', ')" - exit 1 - } - - # Copy the selected bundle to a dedicated directory for store packaging - $storeBundleDir = '$(Pipeline.Workspace)\releasePipeline\msix\store-bundle' - New-Item $storeBundleDir -Type Directory -Force > $null - Copy-Item -Path $bundleFile.FullName -Destination $storeBundleDir -Force -Verbose - Write-Host "##vso[task.setvariable variable=StoreBundleDir]$storeBundleDir" - Write-Verbose -Verbose "Selected bundle for store packaging: $($bundleFile.Name)" - - # These variables are used in the next tasks to determine which ServiceEndpoint to use - $ltsValue = $IsLTS.ToString().ToLower() - $stableValue = $IsStable.ToString().ToLower() - $previewValue = $IsPreview.ToString().ToLower() - - Write-Verbose -Verbose "About to set variables:" - Write-Verbose -Verbose " LTS=$ltsValue" - Write-Verbose -Verbose " STABLE=$stableValue" - Write-Verbose -Verbose " PREVIEW=$previewValue" - - Write-Host "##vso[task.setvariable variable=LTS]$ltsValue" - Write-Host "##vso[task.setvariable variable=STABLE]$stableValue" - Write-Host "##vso[task.setvariable variable=PREVIEW]$previewValue" - - Write-Verbose -Verbose "Variables set successfully" - name: UpdateConfigs - displayName: Update PDPs and SBConfig.json - - - pwsh: | - Write-Verbose -Verbose "Checking variables after UpdateConfigs:" - Write-Verbose -Verbose "LTS=$(LTS)" - Write-Verbose -Verbose "STABLE=$(STABLE)" - Write-Verbose -Verbose "PREVIEW=$(PREVIEW)" - displayName: Debug - Check Variables - - - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 - displayName: 'Create StoreBroker Package (Preview)' - condition: eq(variables['PREVIEW'], 'true') - inputs: - serviceEndpoint: 'StoreAppPublish-Preview' - sbConfigPath: '$(SBConfigPath)' - sourceFolder: '$(StoreBundleDir)' - contents: '*.msixBundle' - outSBName: 'PowerShellStorePackage' - pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' - pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' - - - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 - displayName: 'Create StoreBroker Package (Stable/LTS)' - condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) - inputs: - serviceEndpoint: 'StoreAppPublish-Stable' - sbConfigPath: '$(SBConfigPath)' - sourceFolder: '$(StoreBundleDir)' - contents: '*.msixBundle' - outSBName: 'PowerShellStorePackage' - pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' - pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' - - - pwsh: | - Get-Item -Path "$(System.DefaultWorkingDirectory)/SBLog.txt" -ErrorAction SilentlyContinue | - Copy-Item -Destination "$(ob_outputDirectory)" -Verbose - displayName: Upload Store Failure Log - condition: failed() - - - pwsh: | - $submissionPackageDir = "$(System.DefaultWorkingDirectory)/SBOutDir" - $jsonFile = "$submissionPackageDir/PowerShellStorePackage.json" - $zipFile = "$submissionPackageDir/PowerShellStorePackage.zip" - - if ((Test-Path $jsonFile) -and (Test-Path $zipFile)) { - Write-Verbose -Verbose "Uploading StoreBroker Package files:" - Write-Verbose -Verbose "JSON File: $jsonFile" - Write-Verbose -Verbose "ZIP File: $zipFile" - - Copy-Item -Path $submissionPackageDir -Destination "$(ob_outputDirectory)" -Verbose -Recurse - } - - else { - Write-Error "Required files not found in $submissionPackageDir" - } - displayName: 'Upload StoreBroker Package' diff --git a/.pipelines/templates/package-store-package.yml b/.pipelines/templates/package-store-package.yml new file mode 100644 index 00000000000..7667b1361e7 --- /dev/null +++ b/.pipelines/templates/package-store-package.yml @@ -0,0 +1,242 @@ +jobs: +- job: CreateStorePackage + displayName: Create StoreBroker Package + pool: + type: windows + + variables: + - group: 'Azure Blob variable group' + - group: 'Store Publish Variables' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_codeSignValidation_enabled + value: false + + steps: + - checkout: self + clean: true + + - template: release-SetReleaseTagandContainerName.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_msixbundle_CreateMSIXBundle + itemPattern: | + **/*.msixbundle + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download signed msixbundle + + - pwsh: | + $bundleDir = '$(Build.ArtifactStagingDirectory)/downloads' + $bundle = Get-ChildItem -Path $bundleDir -Filter '*.msixbundle' -Recurse | Select-Object -First 1 + if (-not $bundle) { + Write-Error "No .msixbundle file found in $bundleDir" + exit 1 + } + Write-Verbose -Verbose "Found bundle: $($bundle.FullName)" + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$($bundle.DirectoryName)" + Write-Host "##$vstsCommandString" + displayName: Locate msixbundle + + - template: channelSelection.yml@self + + - pwsh: | + $IsLTS = '$(ChannelSelection.IsLTS)' -eq 'true' + $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' + $IsPreview = '$(ChannelSelection.IsPreview)' -eq 'true' + + Write-Verbose -Verbose "Channel Selection - LTS: $IsLTS, Stable: $IsStable, Preview: $IsPreview" + + # Define app configurations for each channel + $channelConfigs = @{ + 'LTS' = @{ + AppStoreName = 'PowerShell-LTS' + ProductId = '$(productId-LTS)' + AppId = '$(AppID-LTS)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Stable' = @{ + AppStoreName = 'PowerShell' + ProductId = '$(productId-Stable)' + AppId = '$(AppID-Stable)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Preview' = @{ + AppStoreName = 'PowerShell (Preview)' + ProductId = '$(productId-Preview)' + AppId = '$(AppID-Preview)' + ServiceEndpoint = "StoreAppPublish-Preview" + } + } + + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + $config = $channelConfigs[$currentChannel] + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "App Store Name: $($config.AppStoreName)" + Write-Verbose -Verbose "Product ID: $($config.ProductId)" + + # Update PDP.xml file + $pdpPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP/en-US/PDP.xml' + if (Test-Path $pdpPath) { + Write-Verbose -Verbose "Updating PDP file: $pdpPath" + + [xml]$pdpXml = Get-Content $pdpPath -Raw + + # Create namespace manager for XML with default namespace + $nsManager = New-Object System.Xml.XmlNamespaceManager($pdpXml.NameTable) + $nsManager.AddNamespace("pd", "http://schemas.microsoft.com/appx/2012/ProductDescription") + + $appStoreNameElement = $pdpXml.SelectSingleNode("//pd:AppStoreName", $nsManager) + if ($appStoreNameElement) { + $appStoreNameElement.SetAttribute("_locID", $config.AppStoreName) + Write-Verbose -Verbose "Updated AppStoreName _locID to: $($config.AppStoreName)" + } else { + Write-Warning "AppStoreName element not found in PDP file" + } + + $pdpXml.Save($pdpPath) + Write-Verbose -Verbose "PDP file updated successfully" + Get-Content -Path $pdpPath | Write-Verbose -Verbose + } else { + Write-Error "PDP file not found: $pdpPath" + exit 1 + } + + # Update SBConfig.json file + $sbConfigPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/SBConfig.json' + if (Test-Path $sbConfigPath) { + Write-Verbose -Verbose "Updating SBConfig file: $sbConfigPath" + + $sbConfigJson = Get-Content $sbConfigPath -Raw | ConvertFrom-Json + + $sbConfigJson.appSubmission.productId = $config.ProductId + Write-Verbose -Verbose "Updated productId to: $($config.ProductId)" + + $sbConfigJson | ConvertTo-Json -Depth 100 | Set-Content $sbConfigPath -Encoding UTF8 + Write-Verbose -Verbose "SBConfig file updated successfully" + Get-Content -Path $sbConfigPath | Write-Verbose -Verbose + } else { + Write-Error "SBConfig file not found: $sbConfigPath" + exit 1 + } + + Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" + Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" + + # Select the correct bundle based on channel + $bundleFiles = @(Get-ChildItem -Path '$(BundleDir)' -Filter '*.msixbundle') + Write-Verbose -Verbose "Available bundles: $($bundleFiles.Name -join ', ')" + + if ($IsLTS) { + $bundleFile = $bundleFiles | Where-Object { $_.Name -match '-LTS-' } + } else { + # Catches Stable or Preview + $bundleFile = $bundleFiles | Where-Object { $_.Name -notmatch '-LTS-' } + } + + if (-not $bundleFile) { + Write-Error "No matching bundle found for channel '$currentChannel'. Available bundles: $($bundleFiles.Name -join ', ')" + exit 1 + } + + # Copy the selected bundle to a dedicated directory for store packaging + $storeBundleDir = '$(Pipeline.Workspace)\releasePipeline\msix\store-bundle' + New-Item $storeBundleDir -Type Directory -Force > $null + Copy-Item -Path $bundleFile.FullName -Destination $storeBundleDir -Force -Verbose + Write-Host "##vso[task.setvariable variable=StoreBundleDir]$storeBundleDir" + Write-Verbose -Verbose "Selected bundle for store packaging: $($bundleFile.Name)" + + # These variables are used in the next tasks to determine which ServiceEndpoint to use + $ltsValue = $IsLTS.ToString().ToLower() + $stableValue = $IsStable.ToString().ToLower() + $previewValue = $IsPreview.ToString().ToLower() + + Write-Verbose -Verbose "About to set variables:" + Write-Verbose -Verbose " LTS=$ltsValue" + Write-Verbose -Verbose " STABLE=$stableValue" + Write-Verbose -Verbose " PREVIEW=$previewValue" + + Write-Host "##vso[task.setvariable variable=LTS]$ltsValue" + Write-Host "##vso[task.setvariable variable=STABLE]$stableValue" + Write-Host "##vso[task.setvariable variable=PREVIEW]$previewValue" + + Write-Verbose -Verbose "Variables set successfully" + name: UpdateConfigs + displayName: Update PDPs and SBConfig.json + + - pwsh: | + Write-Verbose -Verbose "Checking variables after UpdateConfigs:" + Write-Verbose -Verbose "LTS=$(LTS)" + Write-Verbose -Verbose "STABLE=$(STABLE)" + Write-Verbose -Verbose "PREVIEW=$(PREVIEW)" + displayName: Debug - Check Variables + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Preview)' + condition: eq(variables['PREVIEW'], 'true') + inputs: + serviceEndpoint: 'StoreAppPublish-Preview' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(StoreBundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Stable/LTS)' + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) + inputs: + serviceEndpoint: 'StoreAppPublish-Stable' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(StoreBundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + + - pwsh: | + $outputDirectory = "$(ob_outputDirectory)" + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + } + + Get-Item -Path "$(System.DefaultWorkingDirectory)/SBLog.txt" -ErrorAction SilentlyContinue | + Copy-Item -Destination $outputDirectory -Verbose + displayName: Upload Store Failure Log + condition: failed() + + - pwsh: | + $outputDirectory = "$(ob_outputDirectory)" + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + } + + $submissionPackageDir = "$(System.DefaultWorkingDirectory)/SBOutDir" + $jsonFile = "$submissionPackageDir/PowerShellStorePackage.json" + $zipFile = "$submissionPackageDir/PowerShellStorePackage.zip" + + if ((Test-Path $jsonFile) -and (Test-Path $zipFile)) { + Write-Verbose -Verbose "Uploading StoreBroker Package files:" + Write-Verbose -Verbose "JSON File: $jsonFile" + Write-Verbose -Verbose "ZIP File: $zipFile" + + Copy-Item -Path $submissionPackageDir -Destination $outputDirectory -Verbose -Recurse + } + else { + Write-Error "Required files not found in $submissionPackageDir" + exit 1 + } + displayName: 'Upload StoreBroker Package' diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml index aaef3c6f269..cbbdb70cc4f 100644 --- a/.pipelines/templates/release-MSIX-Publish.yml +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -12,7 +12,7 @@ jobs: inputs: - input: pipelineArtifact pipeline: PSPackagesOfficial - artifactName: drop_msixbundle_CreateMSIXBundle + artifactName: drop_store_package_CreateStorePackage variables: - group: 'Store Publish Variables' - name: LTS @@ -107,28 +107,32 @@ jobs: - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 displayName: 'Publish StoreBroker Package (Stable/LTS)' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true'))) - continueOnError: true + condition: and(not(${{ parameters.skipMSIXPublish }}), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true'))) inputs: serviceEndpoint: 'StoreAppPublish-Stable' appId: '$(AppID)' inputMethod: JsonAndZip jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + force: true + deletePackages: true numberOfPackagesToKeep: 2 jsonZipUpdateMetadata: true targetPublishMode: 'Immediate' + skipPolling: true - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 displayName: 'Publish StoreBroker Package (Preview)' - condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['PREVIEW'], 'true')) - continueOnError: true + condition: and(not(${{ parameters.skipMSIXPublish }}), eq(variables['PREVIEW'], 'true')) inputs: serviceEndpoint: 'StoreAppPublish-Preview' appId: '$(AppID)' inputMethod: JsonAndZip jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + force: true + deletePackages: true numberOfPackagesToKeep: 2 jsonZipUpdateMetadata: true targetPublishMode: 'Immediate' + skipPolling: true diff --git a/.pipelines/templates/stages/PowerShell-Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml index ff40941e31b..b1efb2a8097 100644 --- a/.pipelines/templates/stages/PowerShell-Packages-Stages.yml +++ b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml @@ -173,6 +173,12 @@ stages: parameters: OfficialBuild: ${{ parameters.OfficialBuild }} +- stage: store_package + displayName: 'Store Package' + dependsOn: [msixbundle] + jobs: + - template: /.pipelines/templates/package-store-package.yml@self + - stage: upload displayName: 'Upload' dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON From a625d2d451515542e912e50ab1a818d0c3b9e0b4 Mon Sep 17 00:00:00 2001 From: Dongbo Wang <dongbow@microsoft.com> Date: Fri, 3 Apr 2026 12:42:34 -0700 Subject: [PATCH 242/255] Delay update notification for one week to ensure all packages become available (#27095) --- .../host/msh/UpdatesNotification.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs index b7c001db710..eb4557c04d2 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -28,6 +28,9 @@ internal static class UpdatesNotification private const string StableBuildInfoURL = "https://aka.ms/pwsh-buildinfo-stable"; private const string PreviewBuildInfoURL = "https://aka.ms/pwsh-buildinfo-preview"; + private const int NotificationDelayDays = 7; + private const int UpdateCheckBackoffDays = 7; + /// <summary> /// The version of new update is persisted using a file, not as the file content, but instead baked in the file name in the following template: /// `update{notification-type}_{version}_{publish-date}` -- held by 's_updateFileNameTemplate', @@ -89,9 +92,18 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) if (TryParseUpdateFile( updateFilePath: out _, out SemanticVersion lastUpdateVersion, - lastUpdateDate: out _) + out DateTime lastUpdateDate) && lastUpdateVersion != null) { + DateTime today = DateTime.UtcNow; + if ((today - lastUpdateDate).TotalDays < NotificationDelayDays) + { + // The update was out less than 1 week ago and it's possible the packages are still rolling out. + // We only show the notification when the update is at least 1 week old, to reduce the chance that + // users see the notification but cannot get the new update when they try to install it. + return; + } + string releaseTag = lastUpdateVersion.ToString(); string notificationMsgTemplate = s_notificationType == NotificationType.LTS ? ManagedEntranceStrings.LTSUpdateNotificationMessage @@ -169,7 +181,7 @@ internal static async Task CheckForUpdates() out DateTime lastUpdateDate); DateTime today = DateTime.UtcNow; - if (parseSuccess && updateFilePath != null && (today - lastUpdateDate).TotalDays < 7) + if (parseSuccess && updateFilePath != null && (today - lastUpdateDate).TotalDays < UpdateCheckBackoffDays) { // There is an existing update file, and the last update was less than 1 week ago. // It's unlikely a new version is released within 1 week, so we can skip this check. From 22f62e859d30aa3623d24d374110b41a310bbb81 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:58:53 -0700 Subject: [PATCH 243/255] Redo windows image fix to use latest image (#27198) Co-authored-by: Justin Chung <chungjustin@microsoft.com> --- .../variables/PowerShell-Coordinated_Packages-Variables.yml | 2 +- .pipelines/templates/variables/PowerShell-vPack-Variables.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml index de3ac0ba1b6..dd67d509a8a 100644 --- a/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml +++ b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml @@ -39,7 +39,7 @@ variables: - name: LinuxContainerImage value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - name: WindowsContainerImage - value: onebranch.azurecr.io/windows/ltsc2019/vse2022:latest + value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest - name: CDP_DEFINITION_BUILD_COUNT value: $[counter('', 0)] - name: ReleaseTagVar diff --git a/.pipelines/templates/variables/PowerShell-vPack-Variables.yml b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml index 276911a35b3..7f00a5e0e2a 100644 --- a/.pipelines/templates/variables/PowerShell-vPack-Variables.yml +++ b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml @@ -19,7 +19,7 @@ variables: - name: BuildConfiguration value: Release - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - name: Codeql.Enabled value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack - name: DOTNET_CLI_TELEMETRY_OPTOUT From 601f0167e0a5229b2e9a6b145d0a382432bab5c9 Mon Sep 17 00:00:00 2001 From: StepSecurity Bot <bot@stepsecurity.io> Date: Tue, 7 Apr 2026 13:29:52 -0700 Subject: [PATCH 244/255] [StepSecurity] ci: Harden GitHub Actions (#27202) Signed-off-by: StepSecurity Bot <bot@stepsecurity.io> --- .github/workflows/copilot-setup-steps.yml | 3 +++ .github/workflows/windows-packaging-reusable.yml | 3 +++ .github/workflows/xunit-tests.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index be2dd55df7d..7c63c9122a8 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -10,6 +10,9 @@ on: paths: - ".github/workflows/copilot-setup-steps.yml" +permissions: + contents: read + jobs: # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. # See https://docs.github.com/en/copilot/customizing-copilot/customizing-the-development-environment-for-copilot-coding-agent diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index 55715c42a4c..8b8df27a4e2 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -13,6 +13,9 @@ env: SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts +permissions: + contents: read + jobs: package: name: ${{ matrix.architecture }} - ${{ matrix.channel }} diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index a1c86bea70a..61d535ec49d 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -14,6 +14,9 @@ on: required: false default: testResults-xunit +permissions: + contents: read + jobs: xunit: name: Run xUnit Tests From 34375e9059c7e5339fdbd5790579b444e3dde37d Mon Sep 17 00:00:00 2001 From: StepSecurity Bot <bot@stepsecurity.io> Date: Tue, 7 Apr 2026 14:13:19 -0700 Subject: [PATCH 245/255] [StepSecurity] ci: Harden GitHub Actions (#27201) Signed-off-by: StepSecurity Bot <bot@stepsecurity.io> --- .github/actions/build/ci/action.yml | 4 ++-- .../get-changed-files/action.yml | 2 +- .../infrastructure/path-filters/action.yml | 2 +- .../actions/test/linux-packaging/action.yml | 8 ++++---- .github/actions/test/nix/action.yml | 6 +++--- .../test/process-pester-results/action.yml | 2 +- .github/actions/test/windows/action.yml | 4 ++-- .github/workflows/analyze-reusable.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/labels.yml | 4 ++-- .github/workflows/linux-ci.yml | 18 +++++++++--------- .github/workflows/macos-ci.yml | 18 +++++++++--------- .github/workflows/verify-markdown-links.yml | 2 +- .github/workflows/windows-ci.yml | 12 ++++++------ .../workflows/windows-packaging-reusable.yml | 6 +++--- .github/workflows/xunit-tests.yml | 6 +++--- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.github/actions/build/ci/action.yml b/.github/actions/build/ci/action.yml index be9c0ecd20b..65331fb3185 100644 --- a/.github/actions/build/ci/action.yml +++ b/.github/actions/build/ci/action.yml @@ -13,7 +13,7 @@ runs: if: github.event_name != 'PullRequest' run: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhmmss"))" shell: pwsh - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: global-json-file: ./global.json - name: Bootstrap @@ -34,7 +34,7 @@ runs: Invoke-CIBuild shell: pwsh - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: build path: ${{ runner.workspace }}/build diff --git a/.github/actions/infrastructure/get-changed-files/action.yml b/.github/actions/infrastructure/get-changed-files/action.yml index c897d4f388d..51631cfe141 100644 --- a/.github/actions/infrastructure/get-changed-files/action.yml +++ b/.github/actions/infrastructure/get-changed-files/action.yml @@ -21,7 +21,7 @@ runs: steps: - name: Get changed files id: get-files - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const eventTypes = '${{ inputs.event-types }}'.split(',').map(t => t.trim()); diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml index 656719262b2..af23540256d 100644 --- a/.github/actions/infrastructure/path-filters/action.yml +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -39,7 +39,7 @@ runs: - name: Check if GitHubWorkflowChanges is present id: filter - uses: actions/github-script@v7.0.1 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 env: FILES_JSON: ${{ steps.get-files.outputs.files }} with: diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index 3a61e0751c7..ce37a38c8b7 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -11,7 +11,7 @@ runs: Show-Environment shell: pwsh - - uses: actions/setup-dotnet@v5 + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: global-json-file: ./global.json @@ -48,21 +48,21 @@ runs: shell: pwsh - name: Upload deb packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: packages-deb path: ${{ runner.workspace }}/packages/*.deb if-no-files-found: ignore - name: Upload rpm packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: packages-rpm path: ${{ runner.workspace }}/packages/*.rpm if-no-files-found: ignore - name: Upload tar.gz packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: packages-tar path: ${{ runner.workspace }}/packages/*.tar.gz diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml index 7f68e71c1f5..ab30e0d9ce6 100644 --- a/.github/actions/test/nix/action.yml +++ b/.github/actions/test/nix/action.yml @@ -29,7 +29,7 @@ runs: shell: pwsh - name: Download Build Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: "${{ github.workspace }}" @@ -42,7 +42,7 @@ runs: Write-LogGroupEnd -Title 'Artifacts Directory' shell: pwsh - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: global-json-file: ./global.json @@ -101,7 +101,7 @@ runs: Write-LogGroupEnd -Title 'Bootstrap' - name: Extract Files - uses: actions/github-script@v7.0.0 + uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0 env: DESTINATION_FOLDER: "${{ github.workspace }}/bins" ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" diff --git a/.github/actions/test/process-pester-results/action.yml b/.github/actions/test/process-pester-results/action.yml index 27b94f6ebcb..44f2037626f 100644 --- a/.github/actions/test/process-pester-results/action.yml +++ b/.github/actions/test/process-pester-results/action.yml @@ -21,7 +21,7 @@ runs: - name: Upload testResults artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: junit-pester-${{ inputs.name }} path: ${{ runner.workspace }}/testResults diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml index 2c41f6aac5c..ddc5da4d664 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -29,7 +29,7 @@ runs: shell: pwsh - name: Download Build Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: "${{ github.workspace }}" @@ -42,7 +42,7 @@ runs: Write-LogGroupEnd -Title 'Artifacts Directory' shell: pwsh - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: global-json-file: .\global.json diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 0fe64afb19a..14651c7cd8e 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -41,7 +41,7 @@ jobs: with: fetch-depth: '0' - - uses: actions/setup-dotnet@v5 + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: global-json-file: ./global.json diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 7c63c9122a8..d78e745a4a9 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -25,7 +25,7 @@ jobs: # You can define any steps you want, and they will run before the agent starts. # If you do not check out your code, Copilot will do this for you. steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 61b5eebb88f..27ceac59bbd 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -18,11 +18,11 @@ jobs: steps: - name: Check out the repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Verify PR has label starting with 'cl-' id: verify-labels - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const labels = context.payload.pull_request.labels.map(label => label.name.toLowerCase()); diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 49d1c0a055f..8f57190b1e9 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -57,7 +57,7 @@ jobs: packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -76,7 +76,7 @@ jobs: contents: read steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check for merge conflict markers uses: "./.github/actions/infrastructure/merge-conflict-checker" @@ -88,7 +88,7 @@ jobs: if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 @@ -103,7 +103,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Unelevated CI @@ -121,7 +121,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Elevated CI @@ -139,7 +139,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Unelevated Others @@ -157,7 +157,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Elevated Others @@ -181,7 +181,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 @@ -255,7 +255,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Linux Packaging diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 5f363e0c265..99b6d8796c6 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -57,7 +57,7 @@ jobs: packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Change Detection id: filter @@ -72,7 +72,7 @@ jobs: if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Build @@ -86,7 +86,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Unelevated CI @@ -104,7 +104,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Elevated CI @@ -122,7 +122,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Unelevated Others @@ -140,7 +140,7 @@ jobs: runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Elevated Others @@ -167,10 +167,10 @@ jobs: - macos-15-large steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - - uses: actions/setup-dotnet@v5 + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: global-json-file: ./global.json - name: Bootstrap packaging @@ -229,7 +229,7 @@ jobs: testResultsFolder: "${{ runner.workspace }}/testResults" - name: Upload package artifact if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: macos-package path: "*.pkg" diff --git a/.github/workflows/verify-markdown-links.yml b/.github/workflows/verify-markdown-links.yml index df37ba3c513..19da648a959 100644 --- a/.github/workflows/verify-markdown-links.yml +++ b/.github/workflows/verify-markdown-links.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Verify markdown links id: verify diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 92bbf2f4c9e..3385a13cb54 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -60,7 +60,7 @@ jobs: packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Change Detection id: filter @@ -75,7 +75,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Build @@ -89,7 +89,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Unelevated CI @@ -107,7 +107,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Elevated CI @@ -125,7 +125,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Unelevated Others @@ -143,7 +143,7 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Elevated Others diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml index 8b8df27a4e2..8d0255d4443 100644 --- a/.github/workflows/windows-packaging-reusable.yml +++ b/.github/workflows/windows-packaging-reusable.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 @@ -64,7 +64,7 @@ jobs: shell: pwsh - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: global-json-file: ./global.json @@ -84,7 +84,7 @@ jobs: - name: Upload Build Artifacts if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} path: | diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml index 61d535ec49d..c643917edd0 100644 --- a/.github/workflows/xunit-tests.yml +++ b/.github/workflows/xunit-tests.yml @@ -23,12 +23,12 @@ jobs: runs-on: ${{ inputs.runner_os }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: global-json-file: ./global.json @@ -49,7 +49,7 @@ jobs: Write-Host "Completed xUnit test run." - name: Upload xUnit results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: always() with: name: ${{ inputs.test_results_artifact_name }} From 0fe8748ea154f59778562010708b97a8db9d7f3f Mon Sep 17 00:00:00 2001 From: Dongbo Wang <dongbow@microsoft.com> Date: Tue, 7 Apr 2026 14:55:30 -0700 Subject: [PATCH 246/255] Change the display name of `PowerShell-LTS` MSIX package to "PowerShell LTS" (#27203) --- tools/packaging/packaging.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 198fb91912e..96c60ae69ed 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -4285,7 +4285,7 @@ function New-MSIXPackage $displayName += ' Preview' } elseif ($LTS) { $ProductName += '-LTS' - $displayName += '-LTS' + $displayName += ' LTS' } Write-Verbose -Verbose "ProductName: $productName" From 58b00b5bb0828dd074aede9a0e3e673842456b70 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:58:10 +0000 Subject: [PATCH 247/255] Pin ready-to-merge.yml reusable workflow to commit SHA (#27204) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> --- .github/workflows/linux-ci.yml | 2 +- .github/workflows/macos-ci.yml | 2 +- .github/workflows/windows-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 8f57190b1e9..77186125a9c 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -244,7 +244,7 @@ jobs: - infrastructure_tests # - analyze if: always() - uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 with: needs_context: ${{ toJson(needs) }} linux_packaging: diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 99b6d8796c6..55d852bb68a 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -243,6 +243,6 @@ jobs: - macos_test_unelevated_ci - macos_test_unelevated_others if: always() - uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 with: needs_context: ${{ toJson(needs) }} diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 3385a13cb54..8a57b8b9726 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -189,6 +189,6 @@ jobs: - analyze - windows_packaging if: always() - uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 with: needs_context: ${{ toJson(needs) }} From 073f4d61bda965385232da22a332d54f6bdfce79 Mon Sep 17 00:00:00 2001 From: Dongbo Wang <dongbow@microsoft.com> Date: Wed, 8 Apr 2026 13:28:20 -0700 Subject: [PATCH 248/255] Build, package, and create VPack for the PowerShell-LTS store package within the same `msixbundle-vpack` pipeline (#150) (#27209) --- .pipelines/MSIXBundle-vPack-Official.yml | 513 +++++++++++++++--- PowerShell.Common.props | 2 +- .../host/msh/ConsoleHost.cs | 2 +- .../host/msh/ManagedEntrance.cs | 4 +- .../resources/ManagedEntranceStrings.resx | 2 +- .../resources/RemotingErrorIdStrings.resx | 2 +- .../resources/TabCompletionStrings.resx | 4 +- .../utils/Telemetry.cs | 8 +- tools/packaging/packaging.psm1 | 4 +- 9 files changed, 450 insertions(+), 91 deletions(-) diff --git a/.pipelines/MSIXBundle-vPack-Official.yml b/.pipelines/MSIXBundle-vPack-Official.yml index 876da9c2aff..08edd0367bd 100644 --- a/.pipelines/MSIXBundle-vPack-Official.yml +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -1,31 +1,56 @@ trigger: none +pr: none parameters: # parameters are shown up in ADO UI in a build queue time - name: 'createVPack' displayName: 'Create and Submit VPack' type: boolean default: true +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' - name: 'debug' displayName: 'Enable debug output' type: boolean default: false -- name: 'ReleaseTagVar' +- name: netiso + displayName: "Network Isolation Policy" type: string - displayName: 'Release Tag Var:' - default: 'fromBranch' + values: + - KS4 + - R1 + - Netlock + default: "R1" -name: msixbundle_vPack_$(date:yyMM).$(date:dd)$(rev:rrr) +name: msixbundle_vPack_$(Build.SourceBranchName)_Prod.True_Create.${{ parameters.createVPack }}_$(date:yyyyMMdd).$(rev:rr) variables: - CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] - system.debug: ${{ parameters.debug }} - BuildSolution: $(Build.SourcesDirectory)\dirs.proj - ReleaseTagVar: ${{ parameters.ReleaseTagVar }} - BuildConfiguration: Release - WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - Codeql.Enabled: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: BuildSolution + value: $(Build.SourcesDirectory)\dirs.proj + - name: BuildConfiguration + value: Release + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: Codeql.Enabled + value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: netiso + value: ${{ parameters.netiso }} + - group: certificate_logical_to_actual # used within signing task + - group: MSIXSigningProfile + - group: msixTools resources: repositories: @@ -34,27 +59,17 @@ resources: name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main - pipelines: - - pipeline: PSPackagesOfficial - source: 'PowerShell-Packages-Official' - trigger: - branches: - include: - - master - - releases/* - extends: template: v2/Microsoft.Official.yml@onebranchTemplates parameters: + platform: + name: 'windows_undocked' # windows undocked featureFlags: WindowsHostVersion: Version: 2022 - platform: - name: 'windows_undocked' # windows undocked - + Network: ${{ variables.netiso }} cloudvault: enabled: false - globalSdl: useCustomPolicy: true # for signing code disableLegacyManifest: true @@ -78,71 +93,417 @@ extends: tsaOptionsFile: .config/tsaoptions.json stages: - - stage: build + - stage: Build_MSIX_Package + displayName: 'Build and create MSIX packages' + dependsOn: [] jobs: - - job: main + - job: Build pool: type: windows + strategy: + matrix: + x64: + Architecture: x64 + arm64: + Architecture: arm64 + variables: + ArtifactPlatform: 'windows' ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_artifactBaseName: drop_build_$(Architecture) + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + # The env variable 'ReleaseTagVar' will be updated in this step. + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + + - pwsh: | + $releaseTag = '$(ReleaseTagVar)' + if ($releaseTag -match '-') { + throw "Never release msixbundle vpack for a preview build. Current version: $releaseTag" + } + + # Check if release tag matches the expected format v#.#.# + $matched = $releaseTag -match '^v\d+\.(\d+)\.\d+$' + if (-not $matched) { + throw "Release tag must be in the format v#.#.#, such as 'v7.4.3'. Current version: $releaseTag" + } + + # Extract minor version and verify it's even (LTS versions only) + $minorVersion = [int]$Matches[1] + if($minorVersion % 2 -ne 0) { + throw "Only release msixbundle vpack for LTS releases. Current version: $releaseTag" + } + displayName: Stop any preview release + env: + ob_restore_phase: true + + ### START BUILD ### + + # Clone the checked out PowerShell repo to '/PowerShell' and set the variable 'PowerShellRoot'. + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) + + # Add CodeQL Init task right before your 'Build' step. + - task: CodeQL3000Init@0 + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + inputs: + Enabled: true + # AnalyzeInPipeline: false = upload results + # AnalyzeInPipeline: true = do not upload results + AnalyzeInPipeline: false + Language: csharp + + - template: /.pipelines/templates/install-dotnet.yml@self + + - pwsh: | + $runtime = switch ($env:Architecture) + { + "x64" { "win7-x64" } + "arm64" { "win-arm64" } + } + + $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path $(Pipeline.Workspace)/Symbols_$(Architecture) -Force + + Start-PSBootstrap -Scenario Package + $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose + + Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore -ReleaseTag $(ReleaseTagVar) + + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $psOptPath = "$outputPath/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + Write-Verbose -Verbose "Verifying pdbs exist in build folder" + $pdbs = Get-ChildItem -Path $buildWithSymbolsPath -Recurse -Filter *.pdb + if ($pdbs.Count -eq 0) { + throw "No pdbs found in build folder" + } + else { + Write-Verbose -Verbose "Found $($pdbs.Count) pdbs in build folder" + $pdbs | ForEach-Object { + Write-Verbose -Verbose "Pdb: $($_.FullName)" + } + + $pdbs | Compress-Archive -DestinationPath '$(ob_outputDirectory)\symbols-$(Architecture).zip' -Update + } + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" + displayName: 'Build Windows Universal - $(Architecture)-$(BuildConfiguration) Symbols folder' + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + # Add CodeQL Finalize task right after your 'Build' step. + - task: CodeQL3000Finalize@0 + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + inputs: + sourceScanPath: '$(PowerShellRoot)\src' + ob_restore_phase: true + + # The signed files will be put in '$(ob_outputDirectory)\Signed-$(Runtime)' after this step. + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + OfficialBuild: true + + ### END OF BUILD ### + + - pwsh: | + Get-ChildItem -Path '$(ob_outputDirectory)\Signed-$(Runtime)' -Recurse | Out-String -Width 9999 + displayName: Capture signed files + condition: succeededOrFailed() + + - pwsh: | + Get-ChildItem -Path env: | Out-String -Width 9999 + displayName: Capture Environment + condition: succeededOrFailed() + + ### START Packaging ### + + - template: /.pipelines/templates/shouldSign.yml@self + parameters: + ob_restore_phase: false + + - pwsh: | + Write-Verbose -Verbose "runtime = '$(Runtime)'" + Write-Verbose -Verbose "RepoRoot = '$(PowerShellRoot)'" + + $runtime = '$(Runtime)' + $repoRoot = '$(PowerShellRoot)' + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + + Find-Dotnet + + $signedFilesPath = '$(ob_outputDirectory)\Signed-$(Runtime)' + $psoptionsFilePath = '$(ob_outputDirectory)\psoptions\psoptions.json' + + Write-Verbose -Verbose "signedFilesPath: $signedFilesPath" + Write-Verbose -Verbose "psoptionsFilePath: $psoptionsFilePath" + + Write-Verbose -Message "checking pwsh exists in $signedFilesPath" -Verbose + if (-not (Test-Path $signedFilesPath\pwsh.exe)) { + throw "pwsh.exe not found in $signedFilesPath" + } + + Write-Verbose -Message "Restoring PSOptions from $psoptionsFilePath" -Verbose + + Restore-PSOptions -PSOptionsPath "$psoptionsFilePath" + Get-PSOptions | Write-Verbose -Verbose + + ## Generated packages are placed in the current directory by default. + Set-Location $repoRoot + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $runtime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + + $msixPkgNameFilter = "PowerShell*.msix" + $msixPkgFile = Get-ChildItem -Path $repoRoot -Filter $msixPkgNameFilter -File + $msixPkgPath = $msixPkgFile.FullName + Write-Verbose -Verbose "Unsigned msix package: $msixPkgPath" + + $pkgDir = '$(ob_outputDirectory)\pkgs' + $null = New-Item -ItemType Directory -Path $pkgDir -Force + Copy-Item -Path $msixPkgPath -Destination $pkgDir -Force -Verbose + displayName: 'Build MSIX Package (Unsigned)' + + ### END OF Packaging ### + + - pwsh: | + Get-ChildItem -Path '$(ob_outputDirectory)\pkgs' -Recurse + displayName: 'List Unsigned Package' + + - stage: Pack_MSIXBundle_And_Sign + displayName: 'Pack and sign MSIXBundle' + dependsOn: [Build_MSIX_Package] + jobs: + - job: Bundle + pool: + type: windows + variables: + ArtifactPlatform: 'windows' + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_artifactBaseName: drop_pack_msixbundle ob_createvpack_enabled: ${{ parameters.createVPack }} - ob_createvpack_packagename: 'PowerShell.app' + ob_createvpack_packagename: 'PowerShell7.Store.app' ob_createvpack_owneralias: 'dongbow' - ob_createvpack_description: 'VPack for the PowerShell Application' - ob_createvpack_targetDestinationDirectory: '$(Destination)' + ob_createvpack_description: 'VPack for the PowerShell 7 Store Application' + ob_createvpack_targetDestinationDirectory: '$(Destination)' ## The value is from the 'CreateVpack' task, used when pulling the generated VPack. ob_createvpack_propsFile: false ob_createvpack_provData: true ob_createvpack_metadata: '$(Build.SourceVersion)' ob_createvpack_versionAs: string - ob_createvpack_version: '$(version)' + ob_createvpack_version: '$(Version)' ob_createvpack_verbose: true steps: - - template: .pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - pwsh: | - Write-Verbose -Verbose 'PowerShell Version: $(version)' - if('$(version)' -match '-') { - throw "Don't release a preview build msixbundle package" - } - displayName: Stop any preview release - - - download: PSPackagesOfficial - artifact: 'drop_msixbundle_CreateMSIXBundle' - displayName: Download package - - - pwsh: | - $payloadDir = '$(Pipeline.Workspace)\PSPackagesOfficial\drop_msixbundle_CreateMSIXBundle' - Get-ChildItem $payloadDir -Recurse | Out-String -Width 150 - $vstsCommandString = "vso[task.setvariable variable=PayloadDir]$payloadDir" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Capture Artifact Listing' - - - pwsh: | - $bundlePackage = Get-ChildItem '$(PayloadDir)\*.msixbundle' - Write-Verbose -Verbose ("MSIX bundle package: " + $bundlePackage.FullName -join ', ') - if ($bundlePackage.Count -ne 1) { - throw "Expected to find 1 MSIX bundle package, but found $($bundlePackage.Count)" - } + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true - if (-not (Test-Path '$(ob_outputDirectory)' -PathType Container)) { - $null = New-Item '$(ob_outputDirectory)' -ItemType Directory -ErrorAction Stop - } + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no - $targetPath = Join-Path '$(ob_outputDirectory)' 'Microsoft.PowerShell_8wekyb3d8bbwe.msixbundle' - Copy-Item -Verbose -Path $bundlePackage.FullName -Destination $targetPath - displayName: 'Stage msixbundle for vpack' + - template: /.pipelines/templates/shouldSign.yml@self - - pwsh: | - Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose - $vpackFiles = Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse - if($vpackFiles.Count -eq 0) { - throw "No files found in $(ob_outputDirectory)" - } - $vpackFiles | Out-String -Width 150 - displayName: Debug Output Directory and Version - condition: succeededOrFailed() + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_x64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download msix for x64 + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_arm64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download msix for arm64 + + # Finds the makeappx tool on the machine. + - pwsh: | + Write-Verbose -Verbose 'PowerShell Version: $(Version)' + $cmd = Get-Command makeappx.exe -ErrorAction Ignore + if ($cmd) { + Write-Verbose -Verbose 'makeappx available in PATH' + $exePath = $cmd.Source + } else { + $makeappx = Get-ChildItem -Recurse 'C:\Program Files (x86)\Windows Kits\10\makeappx.exe' | + Where-Object { $_.DirectoryName -match 'x64' } | + Select-Object -Last 1 + $exePath = $makeappx.FullName + Write-Verbose -Verbose "makeappx was found: $exePath" + } + $vstsCommandString = "vso[task.setvariable variable=MakeAppxPath]$exePath" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Find makeappx tool + retryCountOnTaskFailure: 1 + + - pwsh: | + $sourceDir = '$(Pipeline.Workspace)\releasePipeline\msix' + $null = New-Item -Path $sourceDir -ItemType Directory -Force + + $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse + foreach ($msixFile in $msixFiles) { + $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose + } + + $file = Get-ChildItem $sourceDir | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $pkgName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating $pkgName" + + $makeappx = '$(MakeAppxPath)' + $outputDir = "$sourceDir\output" + New-Item $outputDir -Type Directory -Force > $null + & $makeappx bundle /d $sourceDir /p "$outputDir\$pkgName" + if ($LASTEXITCODE -ne 0) { + throw "makeappx bundle failed with exit code $LASTEXITCODE" + } + + Get-ChildItem -Path $sourceDir -Recurse | Out-String -Width 200 + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Create MsixBundle + retryCountOnTaskFailure: 1 + + - task: onebranch.pipeline.signing@1 + displayName: Sign MsixBundle + inputs: + command: 'sign' + signing_profile: $(MSIXProfile) + files_to_sign: '**/*.msixbundle' + search_root: '$(BundleDir)' + + - pwsh: | + $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File + Write-Verbose -Verbose "Signed bundle: $signedBundle" + + $signature = Get-AuthenticodeSignature -FilePath $signedBundle.FullName + if ($signature.Status -ne 'Valid') { + throw "The bundle file doesn't have a valid signature. Signature status: $($signature.Status)" + } + + if (-not (Test-Path '$(ob_outputDirectory)' -PathType Container)) { + $null = New-Item '$(ob_outputDirectory)' -ItemType Directory -ErrorAction Stop + } + + $targetPath = Join-Path '$(ob_outputDirectory)' 'Microsoft.PowerShell-LTS_8wekyb3d8bbwe.msixbundle' + Copy-Item -Verbose -Path $signedBundle.FullName -Destination $targetPath + + Write-Verbose -Verbose "Uploaded Bundle:" + Get-ChildItem -Path $(ob_outputDirectory) | Out-String -Width 200 -Stream | Write-Verbose -Verbose + displayName: 'Stage msixbundle for VPack' + + - pwsh: | + Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose + $vpackFiles = Get-ChildItem -Path '$(ob_outputDirectory)\*' -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(ob_outputDirectory)" + } + $vpackFiles | Out-String -Width 200 + displayName: Debug Output Directory and Version + condition: succeededOrFailed() + + - stage: Publish_Symbols + displayName: 'Publish Symbols' + dependsOn: [Pack_MSIXBundle_And_Sign] + jobs: + - job: PublishSymbols + pool: + type: windows + variables: + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem Env: | Out-String -Width 9999 + displayName: 'Capture Environment Variables' + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_x64 + itemPattern: | + **/symbols-*.zip + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download symbols for x64 + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_arm64 + itemPattern: | + **/symbols-*.zip + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download symbols for arm64 + + - pwsh: | + $downloadDir = '$(Build.ArtifactStagingDirectory)\downloads' + Write-Verbose -Verbose "Enumerating $downloadDir" + $downloadedArtifacts = Get-ChildItem -Path $downloadDir -Recurse -Filter 'symbols-*.zip' + $downloadedArtifacts | Out-String -Width 9999 + + $expandedRoot = New-Item -Path "$(Pipeline.Workspace)\expanded" -ItemType Directory -Verbose + $downloadedArtifacts | ForEach-Object { + $expandDir = Join-Path $expandedRoot $_.BaseName + Write-Verbose -Verbose "Expanding $($_.FullName) to $expandDir" + $null = New-Item -Path $expandDir -ItemType Directory -Verbose + Expand-Archive -Path $_.FullName -DestinationPath $expandDir -Force + } + + Write-Verbose -Verbose "Enumerating $expandedRoot" + Get-ChildItem -Path $expandedRoot -Recurse | Out-String -Width 9999 + $vstsCommandString = "vso[task.setvariable variable=SymbolsPath]$expandedRoot" + Write-Verbose -Message "$vstsCommandString" -Verbose + Write-Host -Object "##$vstsCommandString" + displayName: Expand and capture symbols folders + + - task: PublishSymbols@2 + condition: and(succeeded(), ${{ parameters.createVPack }}) + inputs: + symbolsFolder: '$(SymbolsPath)' + searchPattern: '**/*.pdb' + indexSources: false + publishSymbols: true + symbolServerType: TeamServices + detailedLog: true diff --git a/PowerShell.Common.props b/PowerShell.Common.props index fa2a5305e32..d71432aed2f 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -163,7 +163,7 @@ <PropertyGroup> <DefineConstants>$(DefineConstants);CORECLR</DefineConstants> - <IsWindows Condition="'$(IsWindows)' =='true' or ( '$(IsWindows)' == '' and '$(OS)' == 'Windows_NT')">true</IsWindows> + <IsWindows Condition="'$(IsWindows)' == 'true' or ('$(IsWindows)' == '' and '$(OS)' == 'Windows_NT')">true</IsWindows> </PropertyGroup> <!-- Define non-windows, all configuration properties --> diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 2d9902d176a..daf69d50251 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -321,7 +321,7 @@ internal static int Start( } s_theConsoleHost.BindBreakHandler(); - PSHost.IsStdOutputRedirected = Console.IsOutputRedirected; + IsStdOutputRedirected = Console.IsOutputRedirected; // Send startup telemetry for ConsoleHost startup ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal", s_cpp.ParametersUsedAsDouble); diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs index 6dfd5d54e6f..acfdea07153 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs @@ -86,9 +86,9 @@ public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = Unmanag int exitCode = 0; try { - var banner = string.Format( + string banner = string.Format( CultureInfo.InvariantCulture, - ManagedEntranceStrings.ShellBannerNonWindowsPowerShell, + ManagedEntranceStrings.ShellBannerPowerShell, PSVersionInfo.GitCommitId); ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2(); diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index 2c0b0154757..44ebf0fb538 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -117,7 +117,7 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> - <data name="ShellBannerNonWindowsPowerShell" xml:space="preserve"> + <data name="ShellBannerPowerShell" xml:space="preserve"> <value>PowerShell {0}</value> </data> <data name="ShellBannerCLMode" xml:space="preserve"> diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index 819270da259..b5905f1cf7a 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -849,7 +849,7 @@ Note that 'Start-Job' is not supported by design in scenarios where PowerShell i <value>The WriteEvents parameter cannot be used without the Wait parameter.</value> </data> <data name="PowerShellVersionNotSupported" xml:space="preserve"> - <value>PowerShell remoting endpoint versioning is not supported on PowerShell Core.</value> + <value>PowerShell remoting endpoint versioning is not supported on PowerShell 7+.</value> </data> <data name="JobManagerRegistrationConstructorError" xml:space="preserve"> <value>The following type cannot be instantiated because its constructor is not public: {0}.</value> diff --git a/src/System.Management.Automation/resources/TabCompletionStrings.resx b/src/System.Management.Automation/resources/TabCompletionStrings.resx index 751e04cfd2b..298de92da3c 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -457,10 +457,10 @@ This must be the last parameter on the #requires statement line.</value> Specifies the minimum version of PowerShell that the script requires.</value> </data> <data name="RequiresPsEditionCoreDescription" xml:space="preserve"> - <value>Specifies that the script requires PowerShell Core to run.</value> + <value>Specifies that the script requires PowerShell 7+ to run.</value> </data> <data name="RequiresPsEditionDesktopDescription" xml:space="preserve"> - <value>Specifies that the script requires Windows PowerShell to run.</value> + <value>Specifies that the script requires Windows PowerShell 5.1 to run.</value> </data> <data name="RequiresModuleSpecModuleNameDescription" xml:space="preserve"> <value>[string] diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index f72ac4eade4..9a67f4bb6ed 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -166,7 +166,7 @@ public static class ApplicationInsightsTelemetry private static readonly string s_uuidPath; /// <summary>Gets a value indicating whether telemetry can be sent.</summary> - public static bool CanSendTelemetry { get; private set; } = false; + public static bool CanSendTelemetry { get; private set; } /// <summary> /// Initializes static members of the <see cref="ApplicationInsightsTelemetry"/> class. @@ -774,11 +774,11 @@ internal static void SendUseTelemetry(string featureName, string detail, double if (string.Compare(featureName, s_subsystemRegistration, true) == 0) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, GetSubsystemName(detail)), value); + SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, GetSubsystemName(detail)), value); } else { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, detail), value); + SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, detail), value); } } @@ -794,7 +794,7 @@ internal static void SendExperimentalUseData(string featureName, string detail) return; } - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalFeatureUse, string.Join(":", featureName, detail)); + SendTelemetryMetric(TelemetryType.ExperimentalFeatureUse, string.Join(":", featureName, detail)); } // Get the experimental feature name. If we can report it, we'll return the name of the feature, otherwise, we'll return "anonymous" diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 96c60ae69ed..361f82769f0 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -4318,8 +4318,7 @@ function New-MSIXPackage Write-Verbose "Using LTS assets" -Verbose } - # Appx manifest needs to be in root of source path, but the embedded version needs to be updated - # cp-459155 is 'CN=Microsoft Windows Store Publisher (Store EKU), O=Microsoft Corporation, L=Redmond, S=Washington, C=US' + # Appx manifest needs to be in root of source path, but the embedded version needs to be updated. # authenticodeFormer is 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' $releasePublisher = 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' @@ -4361,7 +4360,6 @@ function New-MSIXPackage else { Copy-Item -Path "$RepoRoot\assets\$_.png" -Destination "$ProductSourcePath\assets\" } - } if ($PSCmdlet.ShouldProcess("Create .msix package?")) { From 9c1272039cf8eab566a048fbfba8007567983cd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:35:19 -0400 Subject: [PATCH 249/255] Bump actions/checkout from 6.0.0 to 6.0.2 (#27206) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/analyze-reusable.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/scorecards.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml index 14651c7cd8e..bb90de8e264 100644 --- a/.github/workflows/analyze-reusable.yml +++ b/.github/workflows/analyze-reusable.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: '0' diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 79f45e9eb4d..3b610326e44 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index b84ed640102..7b3711666aa 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false From 25e80cefdb14d0c69eaa976314e31d035aeddd73 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:27:13 -0500 Subject: [PATCH 250/255] Fix the package pipeline by adding in PDP-Media directory (#27254) --- .pipelines/store/PDP/PDP-Media/en-US/.gitkeep | 0 .pipelines/templates/package-store-package.yml | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 .pipelines/store/PDP/PDP-Media/en-US/.gitkeep diff --git a/.pipelines/store/PDP/PDP-Media/en-US/.gitkeep b/.pipelines/store/PDP/PDP-Media/en-US/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.pipelines/templates/package-store-package.yml b/.pipelines/templates/package-store-package.yml index 7667b1361e7..6abddae6851 100644 --- a/.pipelines/templates/package-store-package.yml +++ b/.pipelines/templates/package-store-package.yml @@ -195,6 +195,7 @@ jobs: contents: '*.msixBundle' outSBName: 'PowerShellStorePackage' pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 displayName: 'Create StoreBroker Package (Stable/LTS)' @@ -206,6 +207,7 @@ jobs: contents: '*.msixBundle' outSBName: 'PowerShellStorePackage' pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' - pwsh: | $outputDirectory = "$(ob_outputDirectory)" From 30d14eafaa02d2815970b99d14e00de297909b6d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:30:19 -0400 Subject: [PATCH 251/255] Externalize `findMissingNotices` target framework selection with ordered Windows fallback (#27269) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com> Co-authored-by: Travis Plunk <travis.plunk@microsoft.com> --- tools/findMissingNotices.ps1 | 113 ++++++++++++++++++++++---- tools/findMissingNotices.targets.json | 5 ++ 2 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 tools/findMissingNotices.targets.json diff --git a/tools/findMissingNotices.ps1 b/tools/findMissingNotices.ps1 index 884eff50664..5ed931c5caa 100644 --- a/tools/findMissingNotices.ps1 +++ b/tools/findMissingNotices.ps1 @@ -16,6 +16,45 @@ Import-Module "$PSScriptRoot\..\.github\workflows\GHWorkflowHelper" -Force . "$PSScriptRoot\..\tools\buildCommon\startNativeExecution.ps1" . "$PSScriptRoot\clearlyDefined\Find-LastHarvestedVersion.ps1" +$targetsConfigPath = Join-Path -Path $PSScriptRoot -ChildPath 'findMissingNotices.targets.json' +if (-not (Test-Path -LiteralPath $targetsConfigPath)) { + throw "Missing target framework config file '$targetsConfigPath'. Add '/tools/findMissingNotices.targets.json' with 'dotnetTargetName' and 'windowsTargetNames' entries." +} + +try { + $targetsConfig = Get-Content -LiteralPath $targetsConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable -ErrorAction Stop +} catch { + throw "Failed to load target framework config from '$targetsConfigPath'. Ensure the file contains valid JSON. Error: $($_.Exception.Message)" +} + +if ($targetsConfig -isnot [hashtable]) { + throw "Invalid target framework config '$targetsConfigPath': expected a JSON object with 'dotnetTargetName' and 'windowsTargetNames'." +} + +if (-not $targetsConfig.ContainsKey('dotnetTargetName') -or [string]::IsNullOrWhiteSpace($targetsConfig['dotnetTargetName'])) { + throw "Invalid target framework config '$targetsConfigPath': 'dotnetTargetName' must be a non-empty string." +} + +if (-not $targetsConfig.ContainsKey('windowsTargetNames')) { + throw "Invalid target framework config '$targetsConfigPath': 'windowsTargetNames' must be present and must be an array." +} + +if ($null -eq $targetsConfig['windowsTargetNames'] -or $targetsConfig['windowsTargetNames'] -isnot [array]) { + throw "Invalid target framework config '$targetsConfigPath': 'windowsTargetNames' must be an array (empty array is allowed)." +} + +$script:dotnetTargetName = [string]$targetsConfig['dotnetTargetName'] +$script:windowsTargetNames = @() +foreach ($windowsTargetName in $targetsConfig['windowsTargetNames']) { + if ($windowsTargetName -isnot [string] -or [string]::IsNullOrWhiteSpace($windowsTargetName)) { + throw "Invalid target framework config '$targetsConfigPath': every entry in 'windowsTargetNames' must be a non-empty string." + } + + $script:windowsTargetNames += $windowsTargetName +} + +# Empty windowsTargetNames is valid and means "use base target fallback only". + $packageSourceName = 'findMissingNoticesNugetOrg' if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) { $null = Register-PackageSource -Name $packageSourceName -Location https://www.nuget.org/api/v2 -ProviderName NuGet @@ -195,8 +234,7 @@ function Get-CGRegistrations { $registrationChanged = $false - $dotnetTargetName = 'net11.0' - $dotnetTargetNameWin7 = 'net11.0-windows8.0' + $baseTargetName = $script:dotnetTargetName $unixProjectName = 'powershell-unix' $windowsProjectName = 'powershell-win-core' $actualRuntime = $Runtime @@ -204,35 +242,30 @@ function Get-CGRegistrations { switch -regex ($Runtime) { "alpine-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - $neutralTarget = "$dotnetTargetName" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "linux-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - $neutralTarget = "$dotnetTargetName" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "osx-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - $neutralTarget = "$dotnetTargetName" - } - "win-x*" { - $sdkToUse = $winDesktopSdk - $folder = $windowsProjectName - $target = "$dotnetTargetNameWin7|$Runtime" - $neutralTarget = "$dotnetTargetNameWin7" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "win-.*" { + $sdkToUse = $winDesktopSdk $folder = $windowsProjectName - $target = "$dotnetTargetNameWin7|$Runtime" - $neutralTarget = "$dotnetTargetNameWin7" + $target = "$baseTargetName|$actualRuntime" + $neutralTarget = "$baseTargetName" } "modules" { $folder = "modules" $actualRuntime = 'linux-x64' - $target = "$dotnetTargetName|$actualRuntime" - $neutralTarget = "$dotnetTargetName" + $target = "$baseTargetName|$actualRuntime" + $neutralTarget = "$baseTargetName" } Default { throw "Invalid runtime name: $Runtime" @@ -247,6 +280,50 @@ function Get-CGRegistrations { dotnet restore --runtime $actualRuntime "/property:SDKToUse=$sdkToUse" } $null = New-PADrive -Path $PSScriptRoot\..\src\$folder\obj\project.assets.json -Name $folder + + if ($Runtime -like "win-*") { + # Windows target selection is optional and ordered: + # 1. Try full Windows TFMs from config in order. + # 2. Fall back to the base non-Windows TFM if present. + try { + $availableTargets = @(Get-ChildItem -Path "${folder}:/targets" -ErrorAction Stop | Select-Object -ExpandProperty Name) + } catch { + throw "Unable to enumerate available targets for runtime '$Runtime' in '$folder'. Ensure dotnet restore succeeded and project.assets.json contains target data. Error: $($_.Exception.Message)" + } + + $selectedTargetName = $null + + foreach ($windowsTargetName in $script:windowsTargetNames) { + if ($windowsTargetName -in $availableTargets) { + $selectedTargetName = $windowsTargetName + break + } + } + + if (-not $selectedTargetName -and $baseTargetName -in $availableTargets) { + Write-Verbose "No configured windows target matched for '$Runtime'. Falling back to base target '$baseTargetName'." -Verbose + $selectedTargetName = $baseTargetName + } + + if (-not $selectedTargetName) { + Write-Verbose "Available targets for '$folder': $($availableTargets -join ', ')" -Verbose + if ($script:windowsTargetNames.Count -eq 0) { + throw "Unable to find a target for '$Runtime'. Tried fallback base target '$baseTargetName' (no windowsTargetNames configured). Ensure project.assets.json contains this target or update dotnetTargetName in '$targetsConfigPath'." + } + + throw "Unable to find a target for '$Runtime'. Tried configured windowsTargetNames '$($script:windowsTargetNames -join "', '")' and fallback base target '$baseTargetName'. Update '$targetsConfigPath' with a valid windows target from the available list." + } + + $target = "$selectedTargetName|$actualRuntime" + $neutralTarget = $selectedTargetName + } + + # Defensive check: non-Windows paths set targets in the switch block, + # Windows path may override them after inspecting available assets targets. + if (-not $target -or -not $neutralTarget) { + throw "Unable to determine restore targets for runtime '$Runtime'." + } + try { $targets = Get-ChildItem -Path "${folder}:/targets/$target" -ErrorAction Stop | Where-Object { $_.Type -eq 'package' } | select-object -ExpandProperty name $targets += Get-ChildItem -Path "${folder}:/targets/$neutralTarget" -ErrorAction Stop | Where-Object { $_.Type -eq 'project' } | select-object -ExpandProperty name diff --git a/tools/findMissingNotices.targets.json b/tools/findMissingNotices.targets.json new file mode 100644 index 00000000000..a347ee4c3be --- /dev/null +++ b/tools/findMissingNotices.targets.json @@ -0,0 +1,5 @@ +{ + "dotnetTargetName": "net11.0", + "windowsTargetNames": [ + ] +} From 72be78e6beb39c122297478b3d6d0135cbca7367 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:01:35 -0500 Subject: [PATCH 252/255] Remove MSI from publishing pipeline (#27213) Co-authored-by: Justin Chung <chungjustin@microsoft.com> Co-authored-by: Aditya Patwardhan <adityap@microsoft.com> --- .github/CONTRIBUTING.md | 16 +- ...rshell-module-organization.instructions.md | 2 +- .../templates/packaging/windows/package.yml | 19 +- .../templates/packaging/windows/sign.yml | 109 +-- .pipelines/templates/release-githubNuget.yml | 7 + .../release-validate-packagenames.yml | 6 +- .pipelines/templates/uploadToAzure.yml | 20 +- .../RegisterMicrosoftUpdate.ps1 | 70 -- assets/wix/ExeLicense.rtf | 206 ------ assets/wix/Product.wxs | 654 ------------------ assets/wix/WixUIBannerBmp.png | Bin 3855 -> 0 bytes assets/wix/WixUIDialogBmp.png | Bin 30804 -> 0 bytes assets/wix/WixUIInfoIco.png | Bin 1638 -> 0 bytes assets/wix/bundle.wxs | 40 -- build.psm1 | 5 - docs/maintainers/releasing.md | 12 +- .../powershell-win-core.csproj | 2 +- test/perf/benchmarks/assets/compiler.test.ps1 | 20 - .../Installer/WindowsInstaller.Tests.ps1 | 39 -- tools/UpdateDotnetRuntime.ps1 | 28 - tools/ci.psm1 | 66 +- tools/packaging/boms/windows.json | 5 - tools/packaging/packaging.psd1 | 3 - tools/packaging/packaging.psm1 | 470 +------------ 24 files changed, 53 insertions(+), 1746 deletions(-) delete mode 100644 assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 delete mode 100644 assets/wix/ExeLicense.rtf delete mode 100644 assets/wix/Product.wxs delete mode 100644 assets/wix/WixUIBannerBmp.png delete mode 100644 assets/wix/WixUIDialogBmp.png delete mode 100644 assets/wix/WixUIInfoIco.png delete mode 100644 assets/wix/bundle.wxs delete mode 100644 test/powershell/Installer/WindowsInstaller.Tests.ps1 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 776a9a8c60f..35eab8c9b5b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -91,7 +91,7 @@ To run the link-checker, follow these steps: 1. Issues marked as [`First-Time-Issue`][first-time-issue], are identified as being easy and a great way to learn about this project and making contributions. - + ### Finding or creating an issue 1. Follow the instructions in [Contributing to Issues][contribute-issues] to find or open an issue. @@ -264,18 +264,10 @@ Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running * Our CI contains automated spell checking and link checking for Markdown files. If there is any false-positive, [run the spell checker command-line tool in interactive mode](#spell-checking-documentation) to add words to the `.spelling` file. -* Our packaging test may not pass and ask you to update `files.wxs` file if you add/remove/update nuget package references or add/remove assert files. - - You could update the file manually in accordance with messages in the test log file. Or you can use automatically generated file. To get the file you should build the msi package locally: - - ```powershell - Import-Module .\build.psm1 - Start-PSBuild -Clean -CrossGen -PSModuleRestore -Runtime win7-x64 -Configuration Release -ReleaseTag <release tag> - Import-Module .\tools\packaging - Start-PSPackage -Type msi -ReleaseTag <release tag> -WindowsRuntime 'win7-x64' -SkipReleaseChecks - ``` - Last command will report where new file is located. + You could update the `.spelling` file manually in accordance with messages in the test log file, or + [run the spell checker command-line tool in interactive mode](#spell-checking-documentation) + to add the false-positive words directly. #### Pull Request - Workflow diff --git a/.github/instructions/powershell-module-organization.instructions.md b/.github/instructions/powershell-module-organization.instructions.md index 461d19fb5df..9cdba06c364 100644 --- a/.github/instructions/powershell-module-organization.instructions.md +++ b/.github/instructions/powershell-module-organization.instructions.md @@ -83,7 +83,7 @@ PowerShell code in GitHub Actions YAML files should be kept minimal. Move code t **Examples**: - `Start-PSPackage` - Create packages -- `New-MSIPackage` - Create Windows MSI +- `New-MSIXPackage` - Create Windows MSIX - `New-DotnetSdkContainerFxdPackage` - Create container packages **When NOT to use**: diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml index 1cc0e9d0e94..8b0e230b6b8 100644 --- a/.pipelines/templates/packaging/windows/package.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -161,9 +161,9 @@ jobs: } $packageTypes = switch ($runtime) { - 'x64' { @('msi', 'zip', 'msix') } - 'x86' { @('msi', 'zip', 'msix') } - 'arm64' { @('msi', 'zip', 'msix') } + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } 'fxdependent' { 'fxdependent' } 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } 'minsize' { 'min-size' } @@ -193,9 +193,9 @@ jobs: Write-Verbose -Verbose "runtime = '$(Runtime)'" $packageTypes = switch ($runtime) { - 'x64' { @('msi', 'zip', 'msix') } - 'x86' { @('msi', 'zip', 'msix') } - 'arm64' { @('msi', 'zip', 'msix') } + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } 'fxdependent' { 'fxdependent' } 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } 'minsize' { 'min-size' } @@ -205,13 +205,6 @@ jobs: New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } - if ($packageTypes -contains 'msi') { - $msiPkgNameFilter = "powershell-*.msi" - $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "unsigned msiPkgPath: $msiPkgPath" - Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose - } - if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { $zipPkgNameFilter = "powershell-*.zip" $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName diff --git a/.pipelines/templates/packaging/windows/sign.yml b/.pipelines/templates/packaging/windows/sign.yml index f7a2e5e03e8..151b5d676fb 100644 --- a/.pipelines/templates/packaging/windows/sign.yml +++ b/.pipelines/templates/packaging/windows/sign.yml @@ -67,110 +67,25 @@ jobs: - template: /.pipelines/templates/install-dotnet.yml@self - # Import build.psm1 and bootstrap packaging dependencies (WiX Toolset) + # Import build.psm1 and bootstrap packaging dependencies - pwsh: | $repoRoot = "$env:REPOROOT" Import-Module "$repoRoot\build.psm1" Import-Module "$repoRoot\tools\packaging" Write-Verbose -Verbose "Modules imported successfully" - - # Install WiX Toolset for EXE package creation - $isArm64 = '$(Runtime)' -eq 'arm64' - $env:RUNTIME = '$(Runtime)' - Start-PSBootstrap -Scenario Package - displayName: 'Import modules and install WiX Toolset' + displayName: 'Import modules' env: ob_restore_phase: true - # Sign MSI packages - - task: onebranch.pipeline.signing@1 - displayName: Sign MSI packages - inputs: - command: 'sign' - signing_profile: external_distribution - files_to_sign: '**\*.msi' - search_root: '$(Pipeline.Workspace)' - - # Create EXE package from signed MSI (for x64, x86, arm64 only) - - pwsh: | - $runtime = '$(Runtime)' - Write-Verbose -Verbose "runtime = '$(Runtime)'" - - $repoRoot = "$env:REPOROOT" - Import-Module "$repoRoot\build.psm1" - Import-Module "$repoRoot\tools\packaging" - - $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') - - if ($runtime -in $noExeRuntimes) { - Write-Verbose -Verbose "No EXE generated for $runtime" - return - } - - $version = '$(Version)' - - $msiLocation = Get-ChildItem -Path $(Pipeline.Workspace) -Recurse -Filter "powershell-*$runtime.msi" | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiLocation: $msiLocation" - - Set-Location $repoRoot - - $exePath = New-ExePackage -ProductVersion $version -ProductTargetArchitecture $runtime -MsiLocationPath $msiLocation - Write-Verbose -Verbose "setting vso[task.setvariable variable=exePath]$exePath" - Write-Host "##vso[task.setvariable variable=exePath]$exePath" - Write-Verbose -Verbose "exePath: $exePath" - - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - Expand-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime - displayName: 'Make exe and expand package' - - # Sign EXE engine - - task: onebranch.pipeline.signing@1 - displayName: Sign exe engine - inputs: - command: 'sign' - signing_profile: $(msft_3rd_party_cert_id) - files_to_sign: '$(System.ArtifactsDirectory)\unsignedEngine\*.exe' - search_root: '$(Pipeline.Workspace)' - - # Compress signed EXE engine back into package - - pwsh: | - $runtime = '$(Runtime)' - Write-Verbose -Verbose "runtime = '$(Runtime)'" - $repoRoot = "$env:REPOROOT" - Import-Module "$repoRoot\build.psm1" - Import-Module "$repoRoot\tools\packaging" - - $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') - - if ($runtime -in $noExeRuntimes) { - Write-Verbose -Verbose "No EXE generated for $runtime" - return - } - - $exePath = '$(exePath)' - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - $enginePath | Get-AuthenticodeSignature | out-string | Write-Verbose -verbose - Compress-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime - displayName: Compress signed exe package - - # Sign final EXE packages - - task: onebranch.pipeline.signing@1 - displayName: Sign exe packages - inputs: - command: 'sign' - signing_profile: external_distribution - files_to_sign: '**\*.exe' - search_root: '$(Pipeline.Workspace)' - # Copy all signed packages to output directory - pwsh: | $runtime = '$(Runtime)' Write-Verbose -Verbose "runtime = '$(Runtime)'" $packageTypes = switch ($runtime) { - 'x64' { @('msi', 'zip', 'msix', 'exe') } - 'x86' { @('msi', 'zip', 'msix', 'exe') } - 'arm64' { @('msi', 'zip', 'msix', 'exe') } + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } 'fxdependent' { 'fxdependent' } 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } 'minsize' { 'min-size' } @@ -180,20 +95,6 @@ jobs: New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } - if ($packageTypes -contains 'msi') { - $msiPkgNameFilter = "powershell-*.msi" - $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "signed msiPkgPath: $msiPkgPath" - Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose - } - - if ($packageTypes -contains 'exe') { - $exePkgNameFilter = "powershell-*.exe" - $exePkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $exePkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "signed exePkgPath: $exePkgPath" - Copy-Item -Path $exePkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose - } - if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { $zipPkgNameFilter = "powershell-*.zip" $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName diff --git a/.pipelines/templates/release-githubNuget.yml b/.pipelines/templates/release-githubNuget.yml index 95698554c40..e7c36a49fe6 100644 --- a/.pipelines/templates/release-githubNuget.yml +++ b/.pipelines/templates/release-githubNuget.yml @@ -43,6 +43,13 @@ jobs: $exefiles | Remove-Item -Force -Verbose } + # The .msi packages should not be uploaded to GitHub release. + $msifiles = Get-ChildItem -Path $Path -Filter *.msi + if ($msifiles) { + Write-Verbose -Verbose "Remove .msi packages:" + $msifiles | Remove-Item -Force -Verbose + } + $OutputPath = Join-Path $Path 'hashes.sha256' $packages = Get-ChildItem -Path $Path -Include * -Recurse -File $checksums = $packages | diff --git a/.pipelines/templates/release-validate-packagenames.yml b/.pipelines/templates/release-validate-packagenames.yml index 6344418cd8f..7271ffc05a8 100644 --- a/.pipelines/templates/release-validate-packagenames.yml +++ b/.pipelines/templates/release-validate-packagenames.yml @@ -94,8 +94,8 @@ jobs: - pwsh: | $message = @() - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -include *.zip, *.msi | ForEach-Object { - if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(msi|zip){1}') + Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -include *.zip | ForEach-Object { + if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(zip){1}') { $messageInstance = "$($_.Name) is not a valid package name" $message += $messageInstance @@ -104,7 +104,7 @@ jobs: } if($message.count -gt 0){throw ($message | out-string)} - displayName: Validate Zip and MSI Package Names + displayName: Validate Zip Package Names - pwsh: | $message = @() diff --git a/.pipelines/templates/uploadToAzure.yml b/.pipelines/templates/uploadToAzure.yml index ce7f26131cc..de6f8e0d7fd 100644 --- a/.pipelines/templates/uploadToAzure.yml +++ b/.pipelines/templates/uploadToAzure.yml @@ -149,10 +149,8 @@ jobs: buildType: 'current' artifact: drop_windows_package_package_win_arm64 itemPattern: | - **/*.msi **/*.msix **/*.zip - **/*.exe targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows arm64 packages @@ -185,10 +183,8 @@ jobs: buildType: 'current' artifact: drop_windows_package_package_win_x64 itemPattern: | - **/*.msi **/*.msix **/*.zip - **/*.exe targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows x64 packages @@ -197,10 +193,8 @@ jobs: buildType: 'current' artifact: drop_windows_package_package_win_x86 itemPattern: | - **/*.msi **/*.msix **/*.zip - **/*.exe targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows x86 packages @@ -246,17 +240,17 @@ jobs: - pwsh: | Write-Verbose -Verbose "Copying Github Release files in $(Build.ArtifactStagingDirectory)/downloads to use in Release Pipeline" - + Write-Verbose -Verbose "Creating output directory for GitHub Release files: $(ob_outputDirectory)/GitHubPackages" New-Item -Path $(ob_outputDirectory)/GitHubPackages -ItemType Directory -Force - Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | - Where-Object { $_.Extension -notin '.msix', '.nupkg' -and $_.Name -notmatch '-gc'} | + Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | + Where-Object { $_.Extension -notin '.msix', '.nupkg' -and $_.Name -notmatch '-gc'} | Copy-Item -Destination $(ob_outputDirectory)/GitHubPackages -Recurse -Verbose - + Write-Verbose -Verbose "Creating output directory for NuGet packages: $(ob_outputDirectory)/NuGetPackages" New-Item -Path $(ob_outputDirectory)/NuGetPackages -ItemType Directory -Force - Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | - Where-Object { $_.Extension -eq '.nupkg' } | + Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | + Where-Object { $_.Extension -eq '.nupkg' } | Copy-Item -Destination $(ob_outputDirectory)/NuGetPackages -Recurse -Verbose displayName: Copy downloads to Artifacts @@ -395,7 +389,7 @@ jobs: } # To use -Include parameter, we need to use \* to get all files - $files = Get-ChildItem -Path $downloadsDirectory\* -Include @("*.deb", "*.tar.gz", "*.rpm", "*.msi", "*.zip", "*.pkg") + $files = Get-ChildItem -Path $downloadsDirectory\* -Include @("*.deb", "*.tar.gz", "*.rpm", "*.zip", "*.pkg") Write-Verbose -Verbose "files to upload." $files | Write-Verbose -Verbose diff --git a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 b/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 deleted file mode 100644 index da89768f74a..00000000000 --- a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -param( - [ValidateSet('Hang', 'Fail')] - $TestHook -) - -$waitTimeoutSeconds = 300 -switch ($TestHook) { - 'Hang' { - $waitTimeoutSeconds = 10 - $jobScript = { Start-Sleep -Seconds 600 } - } - 'Fail' { - $jobScript = { throw "This job script should fail" } - } - default { - $jobScript = { - # This registers Microsoft Update via a predefined GUID with the Windows Update Agent. - # https://learn.microsoft.com/windows/win32/wua_sdk/opt-in-to-microsoft-update - - $serviceManager = (New-Object -ComObject Microsoft.Update.ServiceManager) - $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu - - if (!$isRegistered) { - Write-Verbose -Verbose "Opting into Microsoft Update as the Automatic Update Service" - # 7 is the combination of asfAllowPendingRegistration, asfAllowOnlineRegistration, asfRegisterServiceWithAU - # AU means Automatic Updates - $null = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '') - } - else { - Write-Verbose -Verbose "Microsoft Update is already registered for Automatic Updates" - } - - $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu - - # Return if it was successful, which is the opposite of Pending. - return $isRegistered - } - } -} - -Write-Verbose "Running job script: $jobScript" -Verbose -$job = Start-ThreadJob -ScriptBlock $jobScript - -Write-Verbose "Waiting on Job for $waitTimeoutSeconds seconds" -Verbose -$null = Wait-Job -Job $job -Timeout $waitTimeoutSeconds - -if ($job.State -ne 'Running') { - Write-Verbose "Job finished. State: $($job.State)" -Verbose - $result = Receive-Job -Job $job -Verbose - Write-Verbose "Result: $result" -Verbose - if ($result) { - Write-Verbose "Registration succeeded" -Verbose - exit 0 - } - else { - Write-Verbose "Registration failed" -Verbose - # at the time this was written, the MSI is ignoring the exit code - exit 1 - } -} -else { - Write-Verbose "Job timed out" -Verbose - Write-Verbose "Stopping Job. State: $($job.State)" -Verbose - Stop-Job -Job $job - # at the time this was written, the MSI is ignoring the exit code - exit 258 -} diff --git a/assets/wix/ExeLicense.rtf b/assets/wix/ExeLicense.rtf deleted file mode 100644 index 09b0b1402a5..00000000000 --- a/assets/wix/ExeLicense.rtf +++ /dev/null @@ -1,206 +0,0 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;} -{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} -{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} -{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1032\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f1033\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f1035\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f1036\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f1037\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\f1038\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f1039\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f1040\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\f1372\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}{\f1373\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f1375\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f1376\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;} -{\f1379\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}{\f1380\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f1402\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f1403\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} -{\f1405\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f1406\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f1407\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f1408\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);} -{\f1409\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f1410\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;} -{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);} -{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} -{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} -{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;} -{\*\defchp \f31506\fs24 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \sunhideused \spriority1 Default Paragraph Font;}{\* -\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv -\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\* -\cs15 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid11998831 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid11998831 -Unresolved Mention;}{\s17\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext17 \ssemihidden \sunhideused \styrsid11998831 Normal (Web);}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid203718\rsid534261\rsid10121125\rsid11155465 -\rsid11998831\rsid15422468\rsid15613908}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Travis Plunk}{\operator Travis Plunk} -{\creatim\yr2021\mo2\dy19\hr9\min23}{\revtim\yr2021\mo2\dy19\hr12\min33}{\version4}{\edmins5}{\nofpages1}{\nofwords75}{\nofchars430}{\nofcharsws504}{\vern6021}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} -\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen -\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 -\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct -\asianbrkrule\rsidroot11998831\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 -{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang -{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} -\pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid15422468 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid15422468\charrsid15422468 This application is licensed under the\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465 HYPERLINK "https://aka.ms/PowerShellLicense"}{\rtlch\fcs1 \af0 \ltrch\fcs0 -\insrsid11155465\charrsid15422468 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b5a000000680074007400700073003a002f002f0061006b0061002e006d0073002f0050006f007700650072005300680065006c006c004c006900630065006e00730065000000795881f43b1d7f48af2c825dc4852763 -0000000085ab000000}}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 MIT License}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid15422468\charrsid15422468 -. -\par You may review the\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465 HYPERLINK "https://aka.ms/PowerShellThirdPartyNotices"}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465\charrsid15422468 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b6e000000680074007400700073003a002f002f0061006b0061002e006d0073002f0050006f007700650072005300680065006c006c0054006800690072006400500061007200740079004e006f007400690063006500 -73000000795881f43b1d7f48af2c825dc48527630000000085ab000000}}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 ThirdPartyNotices}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid15422468\charrsid15422468 . -\par }\pard \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid15422468\charrsid15422468 This application collects telemetry under the\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 -\af0 \ltrch\fcs0 \insrsid534261 HYPERLINK "https://aka.ms/PrivacyPolicy"}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid534261 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b52000000680074007400700073003a002f002f0061006b0061002e006d0073002f00500072006900760061006300790050006f006c006900630079000000795881f43b1d7f48af2c825dc48527630000000085ab0000} -}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 Microsoft Privacy Statement}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid15422468\charrsid15422468 -. To opt out, see\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465 HYPERLINK "https://aka.ms/PowerShellTelemetryOptOut" \\o "Original URL:https://github.com/PowerShell/PowerS -hell/blob/master/README.md#telemetryClick to follow link."}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465\charrsid15422468 {\*\datafield -10d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b6a000000680074007400700073003a002f002f0061006b0061002e006d0073002f0050006f007700650072005300680065006c006c00540065006c0065006d0065007400720079004f00700074004f00750074000000 -795881f43b1d7f48af2c825dc48527630000000085ab000000}}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 here}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 -\insrsid15422468\charrsid15422468 .}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11998831 -\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a -9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad -5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 -b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 -0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 -a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f -c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 -0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 -a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 -6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b -4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b -4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b6f4679893070000c9200000160000007468656d652f7468656d652f -7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f2a24fcfda33b6b164873dd648a5eef2547789aad28cc56208de532e81c026e49085bd -ed21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c99e191c3061463074977eefd5afde7bf5de53d5ddcf5e26d4bbc05c1096f6fcfa9d9aefe174ce16248d -7afeb3d9a4d2f13d2151ba4094a5b8e76fb0f03fbbf7eb5fdd454732c609f6403e1547a8e7c752ae8eaa5531876124eeb0154ee1bb25e30992f0caa3ea82a34b -d09bd06aa3566b55134452df4b51026a1f2f97648ebd9952e9dfdb2a1f53784da5500373caa74a35b6243476715e5708b11143cabd0b447b3eccb3609733fc52 -fa1e4542c2173dbfa6fffceabdbb5574940b517940d6909be8bf5c2e17589c37f49c3c3a2b260d823068f50bfd1a40e53e6edc1eb7c6ad429f06a0f91c569a71 -b175b61bc320c71aa0ecd1a17bd41e35eb16ded0dfdce3dc0fd5c7c26b50a63fd8c34f2643b0a285d7a00c1feee1c3417730b2f56b50866fede1dbb5fe28685b -fa3528a6243ddf43d7c25673b85d6d0159327aec8477c360d26ee4ca4b144443115d6a8a254be5a1584bd00bc6270050408a24493db959e1259a43140f112567 -9c7827248a21f056286502866b8ddaa4d684ffea13e827ed5174849121ad780113b137a4f87862cec94af6fc07a0d537206f7ffef9cdeb1fdfbcfee9cd575fbd -79fdf77c6eadca923b466964cafdf2dd1ffef3cd6fbd7ffff0ed2f5fff319b7a172f4cfcbbbffdeedd3ffef93ef5b0e2d2146ffff4fdbb1fbf7ffbe7dfffebaf -5f3bb4f7393a33e1339260e13dc297de5396c0021dfcf119bf9ec42c46c494e8a791402952b338f48f656ca11f6d10450edc00db767cce21d5b880f7d72f2cc2 -d398af2571687c182716f094313a60dc6985876a2ec3ccb3751ab927e76b13f714a10bd7dc43945a5e1eaf579063894be530c616cd2714a5124538c5d253dfb1 -738c1dabfb8210cbaea764ce99604be97d41bc01224e93ccc899154da5d03149c02f1b1741f0b7659bd3e7de8051d7aa47f8c246c2de40d4417e86a965c6fb68 -2d51e252394309350d7e8264ec2239ddf0b9891b0b099e8e3065de78818570c93ce6b05ec3e90f21cdb8dd7e4a37898de4929cbb749e20c64ce4889d0f6394ac -5cd829496313fbb938871045de13265df05366ef10f50e7e40e941773f27d872f787b3c133c8b026a53240d4376beef0e57dccacf89d6ee8126157aae9f3c44a -b17d4e9cd131584756689f604cd1255a60ec3dfbdcc160c05696cd4bd20f62c82ac7d815580f901dabea3dc5027a25d5dcece7c91322ac909de2881de073bad9 -493c1b9426881fd2fc08bc6eda7c0ca52e7105c0633a3f37818f08f480102f4ea33c16a0c308ee835a9fc4c82a60ea5db8e375c32dff5d658fc1be7c61d1b8c2 -be04197c6d1948eca6cc7b6d3343d49aa00c9819822ec3956e41c4727f29a28aab165b3be596f6a62ddd00dd91d5f42424fd6007b4d3fb84ffbbde073a8cb77f -f9c6b10f3e4ebfe3566c25ab6b763a8792c9f14e7f7308b7dbd50c195f904fbfa919a175fa04431dd9cf58b73dcd6d4fe3ffdff73487f6f36d2773a8dfb8ed64 -7ce8306e3b99fc70e5e3743265f3027d8d3af0c80e7af4b14f72f0d46749289dca0dc527421ffc08f83db398c0a092d3279eb838055cc5f0a8ca1c4c60e1228e -b48cc799fc0d91f134462b381daafb4a492472d591f0564cc0a1911e76ea5678ba4e4ed9223becacd7d5c16656590592e5782d2cc6e1a04a66e856bb3cc02bd4 -6bb6913e68dd1250b2d721614c6693683a48b4b783ca48fa58178ce620a157f65158741d2c3a4afdd6557b2c805ae115f8c1edc1cff49e1f06200242701e07cd -f942f92973f5d6bbda991fd3d3878c69450034d8db08283ddd555c0f2e4fad2e0bb52b78da2261849b4d425b46377822869fc17974aad1abd0b8aeafbba54b2d -7aca147a3e08ad9246bbf33e1637f535c8ede6069a9a9982a6de65cf6f35430899395af5fc251c1ac363b282d811ea3717a211dcbccc25cf36fc4d32cb8a0b39 -4222ce0cae934e960d122231f728497abe5a7ee1069aea1ca2b9d51b90103e59725d482b9f1a3970baed64bc5ce2b934dd6e8c284b67af90e1b35ce1fc568bdf -1cac24d91adc3d8d1797de195df3a708422c6cd795011744c0dd413db3e682c0655891c8caf8db294c79da356fa3740c65e388ae62945714339967709dca0b3a -faadb081f196af190c6a98242f8467912ab0a651ad6a5a548d8cc3c1aafb6121653923699635d3ca2aaa6abab39835c3b60cecd8f26645de60b53531e434b3c2 -67a97b37e576b7b96ea74f28aa0418bcb09fa3ea5ea12018d4cac92c6a8af17e1a56393b1fb56bc776811fa07695226164fdd656ed8edd8a1ae19c0e066f54f9 -416e376a6168b9ed2bb5a5f5adb979b1cdce5e40f2184197bba6526857c2c92e47d0104d754f92a50dd8222f65be35e0c95b73d2f3bfac85fd60d80887955a27 -1c57826650ab74c27eb3d20fc3667d1cd66ba341e31514161927f530bbb19fc00506dde4f7f67a7cefee3ed9ded1dc99b3a4caf4dd7c5513d777f7f5c6e1bb7b -8f40d2f9b2d598749bdd41abd26df627956034e854bac3d6a0326a0ddba3c9681876ba9357be77a1c141bf390c5ae34ea5551f0e2b41aba6e877ba9576d068f4 -8376bf330efaaff23606569ea58fdc16605ecdebde7f010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d65 -2f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d36 -3f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e -3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d985 -0528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000000000 -0000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000 -000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019020000 -7468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b6f4679893070000c92000001600000000000000 -000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000 -000000000000000000009d0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000980b00000000} -{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d -617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 -6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 -656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} -{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; -\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid; -\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; -\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; -\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; -\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; -\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; -\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; -\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; -\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; -\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; -\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; -\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; -\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; -\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; -\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; -\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; -\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; -\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; -\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; -\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; -\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore }} \ No newline at end of file diff --git a/assets/wix/Product.wxs b/assets/wix/Product.wxs deleted file mode 100644 index 0b525288ae1..00000000000 --- a/assets/wix/Product.wxs +++ /dev/null @@ -1,654 +0,0 @@ -<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> - <!-- The URL for add/remove programs --> - <!-- TBD:Point to the actual release --> - <?define InfoURL="https://github.com/PowerShell/PowerShell" ?> - <?define ProductSimpleVersionWithNameAndOptionalArchitecture = "$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))"?> - <?if $(var.IsPreview)=True?> - <?define PwshPath = "[VersionFolder]preview"?> - <?define IconPath = "assets\Powershell_av_colors.ico"?> - <?else?> - <?define PwshPath = "[VersionFolder]"?> - <?define IconPath = "assets\Powershell_black.ico"?> - <?endif?> - <!-- UpgradeCode GUID MUST REMAIN SAME THROUGHOUT ALL VERSIONS, otherwise, updates won't occur. --> - <?if $(sys.BUILDARCH)=x64?> - <?define ExplorerContextMenuDialogText = "&$(var.ProductName) $(var.SimpleProductVersion)"?> - <?define UpgradeCodePreview = "39243d76-adaf-42b1-94fb-16ecf83237c8"?> - <?define UpgradeCodeRelease = "31ab5147-9a97-4452-8443-d9709f0516e1"?> - <?if $(var.IsPreview)=True?> - <?define UpgradeCode = $(var.UpgradeCodePreview)?> - <?else?> - <?define UpgradeCode = $(var.UpgradeCodeRelease)?> - <?endif?> - <?elseif $(sys.BUILDARCH) = "ARM64"?> - <?define ExplorerContextMenuDialogText = "&$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))"?> - <?define UpgradeCodePreview = "f064cf16-97b7-4550-b392-ce0f4a6823a3"?> - <?define UpgradeCodeRelease = "75c68ab2-09d8-46b8-b697-d829bdd4c94f"?> - <?if $(var.IsPreview)=True?> - <?define UpgradeCode = $(var.UpgradeCodePreview)?> - <?else?> - <?define UpgradeCode = $(var.UpgradeCodeRelease)?> - <?endif?> - <?else?> - <?define ExplorerContextMenuDialogText = "&$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))"?> - <?define UpgradeCodePreview = "86abcfbd-1ccc-4a88-b8b2-0facfde29094"?> - <?define UpgradeCodeRelease = "1d00683b-0f84-4db8-a64f-2f98ad42fe06"?> - <?if $(var.IsPreview)=True?> - <?define UpgradeCode = $(var.UpgradeCodePreview)?> - <?else?> - <?define UpgradeCode = $(var.UpgradeCodeRelease)?> - <?endif?> - <?endif?> - <!-- Explorer context submenu entries. The ampersand denotes the keyboard shortcut. --> - <?define ExplorerContextSubMenuDialogText = "Open &here"?> - <?define ExplorerContextSubMenuElevatedDialogText = "Open here as &Administrator"?> - <!-- ps1 context submenu entries. --> - <?define PowerShellFileContextSubMenuDialogText = "Run with $(var.ProductName) $(var.SimpleProductVersion)"?> - <!-- Registry location to save installer properties. --> - <?if $(var.IsPreview)=True?> - <?define InstallerPropertiesKey = "Software\Microsoft\PowerShellCore\PreviewInstallerProperties"?> - <?else?> - <?define InstallerPropertiesKey = "Software\Microsoft\PowerShellCore\InstallerProperties"?> - <?endif?> - <Product - Id="*" - Name="PowerShell $(var.SimpleProductVersion)-$(sys.BUILDARCH)" - Language="1033" - Version="$(var.ProductVersion)" - Manufacturer="Microsoft Corporation" - UpgradeCode="$(var.UpgradeCode)"> - <!-- Properties About The Package --> - <Package Id="*" Keywords="Installer" Platform="$(sys.BUILDARCH)" InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Description="PowerShell package" Comments="PowerShell for every system" /> - <!-- Add PowerShell icon for executable --> - <Icon Id="PowerShellExe.ico" SourceFile="$(var.IconPath)" /> - <!-- Add PowerShell icon in Add/Remove Programs --> - <Property Id="ARPPRODUCTICON" Value="PowerShellExe.ico" /> - <!-- Set properties for add/remove programs --> - <Property Id="ARPHELPLINK" Value="$(var.InfoURL)" /> - <!-- custom images --> - <WixVariable Id="WixUIBannerBmp" Value="assets\wix\WixUIBannerBmp.png" /> - <WixVariable Id="WixUIDialogBmp" Value="assets\wix\WixUIDialogBmp.png" /> - <WixVariable Id="WixUIExclamationIco" Value="assets\wix\WixUIInfoIco.png" /> - <WixVariable Id="WixUIInfoIco" Value="assets\ps_black_32x32.ico" /> - <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Open $(var.ProductName)" /> - <!-- Default value of Checkbox of starting PowerShell after installation --> - <Property Id="WixShellExecTarget" Value="[VersionFolder]pwsh.exe"/> - <!-- This changes the default setting from "reinstall if the file is a newer version" to "reinstall if the file is a different version" --> - <Property Id="REINSTALLMODE" Value="dmus"/> - <CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" /> - - <!-- Property Initialization --> - - <!-- - Each property below will undergo the following steps in the - initial sequence (`InstallUISequence` if the UI is shown - or `InstallExecuteSequence` if the UI is skipped): - - 1. Load the value that was used in the previous installation into a separate property. - - 2. `..._SetFromSaved`: If the property does not have a value (meaning it - was not specified on the command line) and there is a value from the - previous installation, use the value from the previous installation. - - 3. `..._SetDefault`: If the property does not have a value (meaning it was not specified - on the command line _and_ there wasn't a value from a previous installation), and - the property has a default value, then set the property to the default value. - - 4. `..._DeleteIfOff`: For "boolean" properties, if the property value is not `1`, then delete - the property. This needs to be done so that the checkbox in the UI is not initially checked. - If the property exists, the checkbox will be initially checked regardless of the property value. - - Then in the `InstallExecuteSequence`: - - 1. `..._SetToOffIfMissing`: For boolean properties, if the property does not exist, it is set - to `0`. This ensures that the property value that is saved to the registry is not blank. - If blank values are saved to the registry, then the `..._SetFromSaved` step won't run because - a blank property value is indistinguishable from a property value that does not exist. - - When using "boolean" properties, always check if the property value is equal `1`, instead - of just checking if the property exists. For example, use `FOO=1` instead of just `FOO`. - --> - - <!-- Property: INSTALLFOLDER --> - <Property Id="PREVIOUS_INSTALLFOLDER"> - <RegistrySearch Id="GetSavedInstallFolder" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="InstallFolder" Type="raw" /> - </Property> - <SetProperty Id="INSTALLFOLDER" Action="InstallFolder_SetFromSaved" After="AppSearch" Value="[PREVIOUS_INSTALLFOLDER]" Sequence="first"> - not INSTALLFOLDER and PREVIOUS_INSTALLFOLDER - </SetProperty> - - <!-- Property: ADD_PATH --> - <Property Id="PREVIOUS_ADD_PATH"> - <RegistrySearch Id="GetSavedAddToPath" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="AddToPath" Type="raw" /> - </Property> - <SetProperty Id="ADD_PATH" Action="AddToPath_SetFromSaved" After="AppSearch" Value="[PREVIOUS_ADD_PATH]" Sequence="first"> - not ADD_PATH and PREVIOUS_ADD_PATH - </SetProperty> - <SetProperty Id="ADD_PATH" Action="AddToPath_SetDefault" After="AddToPath_SetFromSaved" Value="1" Sequence="first"> - not ADD_PATH - </SetProperty> - <SetProperty Id="ADD_PATH" Action="AddToPath_DeleteIfOff" After="AddToPath_SetDefault" Value="{}" Sequence="first"> - ADD_PATH<>1 - </SetProperty> - <SetProperty Id="ADD_PATH" Action="AddToPath_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not ADD_PATH - </SetProperty> - - <!-- Property: REGISTER_MANIFEST --> - <Property Id="PREVIOUS_REGISTER_MANIFEST"> - <RegistrySearch Id="GetSavedRegisterManifest" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="RegisterManifest" Type="raw" /> - </Property> - <SetProperty Id="REGISTER_MANIFEST" Action="RegisterManifest_SetFromSaved" After="AppSearch" Value="[PREVIOUS_REGISTER_MANIFEST]" Sequence="first"> - not REGISTER_MANIFEST and PREVIOUS_REGISTER_MANIFEST - </SetProperty> - <SetProperty Id="REGISTER_MANIFEST" Action="RegisterManifest_SetDefault" After="RegisterManifest_SetFromSaved" Value="1" Sequence="first"> - not REGISTER_MANIFEST - </SetProperty> - <SetProperty Id="REGISTER_MANIFEST" Action="RegisterManifest_DeleteIfOff" After="RegisterManifest_SetDefault" Value="{}" Sequence="first"> - REGISTER_MANIFEST<>1 - </SetProperty> - <SetProperty Id="REGISTER_MANIFEST" Action="RegisterManifest_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not REGISTER_MANIFEST - </SetProperty> - - <!-- Property: ENABLE_PSREMOTING --> - <Property Id="PREVIOUS_ENABLE_PSREMOTING"> - <RegistrySearch Id="GetSavedEnablePsRemoting" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="EnablePsRemoting" Type="raw" /> - </Property> - <SetProperty Id="ENABLE_PSREMOTING" Action="EnablePsRemoting_SetFromSaved" After="AppSearch" Value="[PREVIOUS_ENABLE_PSREMOTING]" Sequence="first"> - not ENABLE_PSREMOTING and PREVIOUS_ENABLE_PSREMOTING - </SetProperty> - <SetProperty Id="ENABLE_PSREMOTING" Action="EnablePsRemoting_DeleteIfOff" After="EnablePsRemoting_SetFromSaved" Value="{}" Sequence="first"> - ENABLE_PSREMOTING<>1 - </SetProperty> - <SetProperty Id="ENABLE_PSREMOTING" Action="EnablePsRemoting_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not ENABLE_PSREMOTING - </SetProperty> - - <!-- Property: DISABLE_TELEMETRY --> - <Property Id="PREVIOUS_DISABLE_TELEMETRY"> - <RegistrySearch Id="GetSavedDisableTelemetry" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="DisableTelemetry" Type="raw" /> - </Property> - <SetProperty Id="DISABLE_TELEMETRY" Action="DisableTelemetry_SetFromSaved" After="AppSearch" Value="[PREVIOUS_DISABLE_TELEMETRY]" Sequence="first"> - not DISABLE_TELEMETRY and PREVIOUS_DISABLE_TELEMETRY - </SetProperty> - <SetProperty Id="DISABLE_TELEMETRY" Action="DisableTelemetry_DeleteIfOff" After="DisableTelemetry_SetFromSaved" Value="{}" Sequence="first"> - DISABLE_TELEMETRY<>1 - </SetProperty> - <SetProperty Id="DISABLE_TELEMETRY" Action="DisableTelemetry_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not DISABLE_TELEMETRY - </SetProperty> - - <!-- Property: ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL --> - <Property Id="PREVIOUS_ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL"> - <RegistrySearch Id="GetSavedAddExplorerContextMenuOpenPowerShell" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="AddExplorerContextMenuOpenPowerShell" Type="raw" /> - </Property> - <SetProperty Id="ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL" Action="AddExplorerContextMenuOpenPowerShell_SetFromSaved" After="AppSearch" Value="[PREVIOUS_ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL]" Sequence="first"> - not ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL and PREVIOUS_ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL - </SetProperty> - <SetProperty Id="ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL" Action="AddExplorerContextMenuOpenPowerShell_DeleteIfOff" After="AddExplorerContextMenuOpenPowerShell_SetFromSaved" Value="{}" Sequence="first"> - ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL<>1 - </SetProperty> - <SetProperty Id="ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL" Action="AddExplorerContextMenuOpenPowerShell_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL - </SetProperty> - - <!-- Property: ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL --> - <Property Id="PREVIOUS_ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL"> - <RegistrySearch Id="GetSavedAddFileContextMenuRunPowerShell" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="AddFileContextMenuRunPowerShell" Type="raw" /> - </Property> - <SetProperty Id="ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL" Action="AddFileContextMenuRunPowerShell_SetFromSaved" After="AppSearch" Value="[PREVIOUS_ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL]" Sequence="first"> - not ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL and PREVIOUS_ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL - </SetProperty> - <SetProperty Id="ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL" Action="AddFileContextMenuRunPowerShell_DeleteIfOff" After="AddFileContextMenuRunPowerShell_SetFromSaved" Value="{}" Sequence="first"> - ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL<>1 - </SetProperty> - <SetProperty Id="ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL" Action="AddFileContextMenuRunPowerShell_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL - </SetProperty> - - <!-- Property: USE_MU --> - <Property Id="PREVIOUS_USE_MU"> - <RegistrySearch Id="GetSavedUseMU" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="UseMU" Type="raw" /> - </Property> - <SetProperty Id="USE_MU" Action="UseMU_SetFromSaved" After="AppSearch" Value="[PREVIOUS_USE_MU]" Sequence="first"> - not USE_MU and PREVIOUS_USE_MU - </SetProperty> - <SetProperty Id="USE_MU" Action="UseMU_SetDefault" After="UseMU_SetFromSaved" Value="1" Sequence="first"> - not USE_MU - </SetProperty> - <SetProperty Id="USE_MU" Action="UseMU_DeleteIfOff" After="UseMU_SetDefault" Value="{}" Sequence="first"> - USE_MU<>1 - </SetProperty> - <SetProperty Id="USE_MU" Action="UseMU_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not USE_MU - </SetProperty> - - <!-- Property: ENABLE_MU --> - <Property Id="PREVIOUS_ENABLE_MU"> - <RegistrySearch Id="GetSavedEnableMU" Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="EnableMU" Type="raw" /> - </Property> - <SetProperty Id="ENABLE_MU" Action="EnableMU_SetFromSaved" After="AppSearch" Value="[PREVIOUS_ENABLE_MU]" Sequence="first"> - not ENABLE_MU and PREVIOUS_ENABLE_MU - </SetProperty> - <SetProperty Id="ENABLE_MU" Action="EnableMU_SetDefault" After="EnableMU_SetFromSaved" Value="1" Sequence="first"> - not ENABLE_MU - </SetProperty> - <SetProperty Id="ENABLE_MU" Action="EnableMU_DeleteIfOff" After="EnableMU_SetDefault" Value="{}" Sequence="first"> - ENABLE_MU<>1 - </SetProperty> - <SetProperty Id="ENABLE_MU" Action="EnableMU_SetToOffIfMissing" Before="InstallInitialize" Value="0" Sequence="execute"> - not ENABLE_MU - </SetProperty> - - <!-- End Property Initialization --> - - <SetProperty Id="RegisterManifest" - Before="RegisterManifest" - Sequence="execute" - Value=""[VersionFolder]pwsh.exe" -NoProfile -ExecutionPolicy Bypass -File "[VersionFolder]RegisterManifest.ps1"" /> - <CustomAction Id="RegisterManifest" - BinaryKey="WixCA" - DllEntry="CAQuietExec" - Execute="deferred" - Return="ignore" - Impersonate="no" /> - <SetProperty Id="EnablePSRemoting" - Before="EnablePSRemoting" - Sequence="execute" - Value=""[VersionFolder]pwsh.exe" -NoProfile -ExecutionPolicy Bypass -Command "Enable-PSRemoting"" /> - <CustomAction Id="EnablePSRemoting" - BinaryKey="WixCA" - DllEntry="CAQuietExec" - Execute="deferred" - Return="ignore" - Impersonate="no" /> - <SetProperty Id="EnableMU" - Before="EnableMU" - Sequence="execute" - Value=""[VersionFolder]pwsh.exe" -NoProfile -ExecutionPolicy Bypass -File "[VersionFolder]RegisterMicrosoftUpdate.ps1"" /> - <CustomAction Id="EnableMU" - BinaryKey="WixCA" - DllEntry="CAQuietExec" - Execute="deferred" - Return="ignore" - Impersonate="no" /> - <InstallExecuteSequence> - <!-- Do not remove shortcuts on upgrade to preserve user shortcuts pinned to Taskbar, see https://stackoverflow.com/a/33402698/1810304 --> - <RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts> - <Custom Action="RegisterManifest" After="InstallFiles"> - <![CDATA[NOT Installed AND (REGISTER_MANIFEST=1)]]> - </Custom> - <Custom Action="EnablePSRemoting" After="InstallFiles"> - <![CDATA[NOT Installed AND (ENABLE_PSREMOTING=1)]]> - </Custom> - <Custom Action="EnableMU" After="InstallFiles"> - <![CDATA[NOT Installed AND (ENABLE_MU=1)]]> - </Custom> - </InstallExecuteSequence> - <UI> - <Dialog Id="MyExitDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)"> - <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="yes" Text="!(loc.ExitDialogBitmap)" /> - <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Bigger}!(loc.Error1707)" TabSkip="yes" /> - <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="20" Transparent="yes" NoPrefix="yes" Text="!(loc.ExitDialogDescription)" TabSkip="yes" /> - <!-- divider between check boxes and button row --> - <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" TabSkip="yes" /> - <!-- Button row --> - <!-- Checkbox to allow starting PowerShell after the installation (in UI mode only) --> - <Control Id="LaunchCheckBox" Type="CheckBox" X="10" Y="243" Width="170" Height="17" Property="LAUNCHAPPONEXIT" Hidden="yes" CheckBoxValue="1" Text="Launch $(var.ProductName)" > - <Condition Action="show">NOT Installed</Condition> - </Control> - <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUIBack)" /> - <Control Id="Finish" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUIFinish)" /> - <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUICancel)" /> - </Dialog> - <InstallUISequence> - <Show Dialog="MyExitDialog" OnExit="success" /> - </InstallUISequence> - <AdminUISequence> - <Show Dialog="MyExitDialog" OnExit="success" /> - </AdminUISequence> - <Publish Dialog="MyExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish> - </UI> - <UI> - <Publish Dialog="MyExitDialog" Control="Finish" Order="1" Event="DoAction" Value="LaunchApplication">LAUNCHAPPONEXIT=1</Publish> - </UI> - <!-- Prerequisites --> - <Condition Message="Supported only on Windows 8.1 and above"> - <![CDATA[ Installed OR MsiNTProductType <> 1 OR VersionNT >= 603 ]]> - </Condition> - <Condition Message="Supported only on Windows Server 2012 and above"> - <![CDATA[ Installed OR MsiNTProductType = 1 OR VersionNT >= 602 ]]> - </Condition> - <!-- Information About When Older Versions Are Trying To Be Installed--> - <MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of $(var.ProductName) is already installed." /> - <!-- Embed Cabinet Files in Product--> - <MediaTemplate EmbedCab="yes" /> - <!-- In Your Wix Setup Project, Add A Reference To WixUIExtension.dll --> - <UIRef Id="CustomWixUI_InstallDir" /> - <!-- Features are mandatory. Need At Least One. --> - <Feature Id="ProductFeature" Title="PowerShell" Level="1"> - <ComponentGroupRef Id="ApplicationFiles"/> - <ComponentRef Id="ApplicationProgramsMenuShortcut"/> - <ComponentRef Id="MURegistryEntries"/> - <ComponentRef Id="RegistryEntries"/> - <?if $(var.IsPreview)!=True?> - <ComponentRef Id="AppPath"/> - <?endif?> - <ComponentRef Id="SetPath"/> - <ComponentRef Id="Telemetry"/> - <ComponentRef Id="DisableTelemetry"/> - <ComponentRef Id="ExplorerContextMenu"/> - <ComponentRef Id="PowerShellFileContextMenu"/> - <ComponentRef Id="SaveInstallerProperties"/> - </Feature> - <!-- We need to show EULA, and provide option to customize download location --> - <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" /> - <!-- Need to get the Windows product name from the registry --> - <Property Id="WINDOWS_PRODUCT_NAME"> - <RegistrySearch Id="ProductName" Root="HKLM" Type="raw" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="ProductName" /> - </Property> - <!-- Prerequisite check for Windows Universal C runtime --> - <Property Id="UNIVERSAL_C_RUNTIME_INSTALLED" Secure="yes"> - <DirectorySearch Id="WindowsDirectory" Path="[WindowsFolder]"> - <DirectorySearch Id="System32" Path="System32"> - <FileSearch Id="ucrtbase" Name="ucrtbase.dll"/> - </DirectorySearch> - </DirectorySearch> - </Property> - <Condition Message="$(var.ProductName) requires the Universal C Runtime to be installed to enable remoting over WinRM. You can find a download link to it here: https://aka.ms/pscore6-prereq"> - <![CDATA[Installed OR UNIVERSAL_C_RUNTIME_INSTALLED]]> - </Condition> - <!-- Prerequisite check for Windows Management Framework --> - <Property Id="PWRSHPLUGIN_VERSION" Secure="yes"> - <DirectorySearchRef Id="System32" Parent="WindowsDirectory" Path="System32"> - <FileSearch Id="pwrshplugin" Name="pwrshplugin.dll" MinVersion="6.3.9600.16383"/> - </DirectorySearchRef> - </Property> - <Condition Message="$(var.ProductName) requires the Windows Management Framework 4.0 or newer to be installed to enable remoting over WinRM. You can find download links here: https://aka.ms/pscore6-prereq"> - <![CDATA[Installed OR PWRSHPLUGIN_VERSION ]]> - </Condition> - <Directory Id="TARGETDIR" Name="SourceDir"> - <Directory Id="$(var.ProductProgFilesDir)"> - <Directory Id="INSTALLFOLDER" Name="PowerShell"> - <Directory Id="VersionFolder" Name="$(var.SimpleProductVersion)"> - <Component Id="Telemetry" Guid="{80520f20-471d-4d08-adc8-bab637627b39}" KeyPath="yes"> - <!-- Should retain component GUID since this is a shared, non-file, non-registry resource. --> - <Environment Id="PowerShellDistributionChannel" Action="create" Name="POWERSHELL_DISTRIBUTION_CHANNEL" Permanent="no" System="yes" Value="MSI:[WINDOWS_PRODUCT_NAME]"/> - </Component> - <Component Id="MURegistryEntries"> - <Condition>USE_MU=1</Condition> - <!-- Create registry key to allow opt-out of MU servicing without opting out of MU completely. We don't want to uninstall because it's used by both Preview and Stable and the detection relies on PS7 being installed for this to take effect. --> - <RegistryKey Root="HKLM" Key="Software\Microsoft\PowerShellCore" Action="create" ForceCreateOnInstall="yes"> - <RegistryValue Type="integer" Value="1" Name="UseMU" KeyPath="yes"/> - </RegistryKey> - </Component> - <Component Id="RegistryEntries"> - <!-- create key for easy detection of a particular version of a powershell core package - The upgrade code is used in the key because it will change when we allow SxS --> - <RegistryKey Root="HKLM" Key="Software\Microsoft\PowerShellCore\InstalledVersions\$(var.UpgradeCode)" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes"> - <RegistryValue Type="string" Value="$(var.ProductSemanticVersion)" Name="SemanticVersion" KeyPath="yes"/> - <RegistryValue Type="string" Value="[VersionFolder]" Name="InstallLocation" /> - <RegistryValue Type="string" Value="[ProductCode]" Name="ProductCode" /> - </RegistryKey> - - </Component> - <?if $(var.IsPreview)!=True?> - <Component Id="AppPath"> - <!-- register ourselves in application registry so can be started using just Win+R `pwsh.exe` --> - <RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\App Paths\pwsh.exe" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes"> - <!-- As a shared path, the last one to install will overwrite any previous value. It also needs to be in its own component to properly ref-count and be removed when appropriate. --> - <RegistryValue Type="string" Value="[VersionFolder]pwsh.exe" KeyPath="yes"/> - </RegistryKey> - </Component> - <?endif?> - <Component Id="DisableTelemetry" Guid="{E86F5C91-3721-4C5C-A31F-1F0A5A468573}" KeyPath="yes"> - <Condition>DISABLE_TELEMETRY=1</Condition> - <Environment Id="PowerShellTelemetryOptout" Action="set" Name="POWERSHELL_TELEMETRY_OPTOUT" Permanent="no" System="yes" Value="1"/> - </Component> - <!-- add ourselves to %PATH% so pwsh.exe can be started from Windows PowerShell or cmd.exe --> - <Component Id="SetPath"> - <Condition>ADD_PATH=1</Condition> - <Environment Id="PATH" Action="set" Name="PATH" Part="last" Permanent="no" System="yes" Value="$(var.PwshPath)"/> - <!-- Can't use a shared component GUID here either since the path we add to PATH varies on the actual directory path. Use a key that represents this same path. --> - <RegistryKey Root="HKLM" Key="Software\Microsoft\PowerShellCore\InstalledVersions\$(var.UpgradeCode)"> - <RegistryValue Type="string" Name="InstallDir" Value="[VersionFolder]" KeyPath="yes"/> - </RegistryKey> - </Component> - <!-- Explorer context menu with 2 submenus to open PowerShell normally or as an Administrator. - See https://blogs.msdn.microsoft.com/andrew_richards/2017/03/01/enhancing-the-open-command-prompt-here-shift-right-click-context-menu-experience/ for details --> - <Component Id="ExplorerContextMenu"> - <Condition>ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1</Condition> - <!-- When clicking on background in Explorer --> - <RegistryKey Root="HKCR" Key="Directory\Background\shell\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - <RegistryValue Type="string" Name="ExtendedSubCommandsKey" Value="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)" KeyPath="yes"/> - </RegistryKey> - <!-- When clicking on Drive in Explorer --> - <RegistryKey Root="HKCR" Key="Drive\shell\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - <RegistryValue Type="string" Name="ExtendedSubCommandsKey" Value="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"/> - </RegistryKey> - <!-- When clicking on Desktop background in Explorer --> - <RegistryKey Root="HKCR" Key="DesktopBackground\shell\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - <RegistryValue Type="string" Name="ExtendedSubCommandsKey" Value="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"/> - </RegistryKey> - <!-- When clicking on folder in Explorer --> - <RegistryKey Root="HKCR" Key="Directory\shell\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - <RegistryValue Type="string" Name="ExtendedSubCommandsKey" Value="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"/> - </RegistryKey> - <!-- When being in a Library folder in Explorer --> - <RegistryKey Root="HKCR" Key="LibraryFolder\background\shell\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - <RegistryValue Type="string" Name="ExtendedSubCommandsKey" Value="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"/> - </RegistryKey> - <!-- Sub menus to open PowerShell at the current location either as a normal shell or as Administrator --> - <RegistryKey Root="HKCR" Key="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)\shell\openpwsh"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextSubMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - </RegistryKey> - <RegistryKey Root="HKCR" Key="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)\shell\openpwsh\command"> - <RegistryValue Type="string" Value="[VersionFolder]pwsh.exe -NoExit -RemoveWorkingDirectoryTrailingCharacter -WorkingDirectory "%V!" -Command "$host.UI.RawUI.WindowTitle = '$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))'""/> - </RegistryKey> - <RegistryKey Root="HKCR" Key="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)\shell\runas"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.ExplorerContextSubMenuElevatedDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe"/> - <RegistryValue Type="string" Value="" Name="HasLUAShield"/> - </RegistryKey> - <RegistryKey Root="HKCR" Key="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)\shell\runas\command"> - <RegistryValue Type="string" Value="[VersionFolder]pwsh.exe -NoExit -RemoveWorkingDirectoryTrailingCharacter -WorkingDirectory "%V!" -Command "$host.UI.RawUI.WindowTitle = '$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))'""/> - </RegistryKey> - <RemoveRegistryKey Id="RemoveOldOpenKey" Root="HKCR" Key="Directory\ContextMenus\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)\shell\open" Action="removeOnInstall"/> - </Component> - <Component Id="PowerShellFileContextMenu"> - <Condition>ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1</Condition> - <!-- When right-clicking ps1 file --> - <RegistryKey Root="HKCR" Key="Microsoft.PowerShellScript.1\Shell\$(var.ProductName)$(var.SimpleProductVersion)$(sys.BUILDARCH)"> - <RegistryValue Type="string" Name="MUIVerb" Value="$(var.PowerShellFileContextSubMenuDialogText)"/> - <RegistryValue Type="string" Name="Icon" Value="[VersionFolder]pwsh.exe" KeyPath="yes"/> - <RegistryKey Key="Command"> - <RegistryValue Type="string" Value="[VersionFolder]pwsh.exe -Command "$host.UI.RawUI.WindowTitle = '$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))'; & '%1'""/> - </RegistryKey> - </RegistryKey> - </Component> - <Component Id="SaveInstallerProperties" Permanent="yes"> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="AddToPath" Value="[ADD_PATH]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="RegisterManifest" Value="[REGISTER_MANIFEST]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="EnablePSRemoting" Value="[ENABLE_PSREMOTING]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="DisableTelemetry" Value="[DISABLE_TELEMETRY]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="AddExplorerContextMenuOpenPowerShell" Value="[ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="AddFileContextMenuRunPowerShell" Value="[ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="UseMU" Value="[USE_MU]" Type="string" /> - <RegistryValue Root="HKLM" Key="$(var.InstallerPropertiesKey)" Name="EnableMU" Value="[ENABLE_MU]" Type="string" /> - </Component> - </Directory> - </Directory> - </Directory> - <Directory Id="ProgramMenuFolder"> - <Directory Id="ApplicationProgramsFolder" Name="$(var.ProductName)"> - <Component Id="ApplicationProgramsMenuShortcut"> - <Shortcut Id="PowerShell_ProgramsMenuShortcut" - Name="$(var.ProductSimpleVersionWithNameAndOptionalArchitecture)" - Description="$(var.ProductSimpleVersionWithNameAndOptionalArchitecture)" - Target="[VersionFolder]pwsh.exe" - Arguments="-WorkingDirectory ~" /> - <RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/> - <!-- HKMU is HKLM when installing perMachine and HKCU when installing perUser--> - <RegistryValue Root="HKMU" Key="Software\Microsoft\PowerShellCore\ProgramsMenuShortcut\$(var.SimpleProductVersion)" Name="installed" Type="integer" Value="1" KeyPath="yes"/> - </Component> - </Directory> - </Directory> - </Directory> - </Product> - -<!-- Explorer Context Menu Dialog --> -<Fragment> - <UI> - <Dialog Id="ExplorerContextMenuDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)"> - <!-- Banner --> - <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="yes" Text="!(loc.InstallDirDlgBannerBitmap)"/> - <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Title}Optional Actions" TabSkip="yes" /> - <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Initialization and Customization" TabSkip="yes" /> - <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" TabSkip="yes" /> - <!-- If the checkboxes are defined first, then they are first in the tab order and can be ticked and unticked using the spacebar --> - <Control Id="AddToPathCheckBox" Type="CheckBox" X="20" Y="60" Width="290" Height="17" Property="ADD_PATH" CheckBoxValue="1" Text="Add $(var.ProductName) to Path Environment Variable"/> - <Control Id="RegisterManifestCheckBox" Type="CheckBox" X="20" Y="80" Width="290" Height="17" Property="REGISTER_MANIFEST" CheckBoxValue="1" Text="Register Windows Event Logging Manifest"/> - <Control Id="EnablePSRemotingCheckBox" Type="CheckBox" X="20" Y="100" Width="290" Height="17" Property="ENABLE_PSREMOTING" CheckBoxValue="1" Text="Enable $(var.ProductName) remoting"/> - <Control Id="DisableTelemetry" Type="CheckBox" X="20" Y="120" Width="290" Height="17" Property="DISABLE_TELEMETRY" CheckBoxValue="1" Text="Disable Telemetry (Reboot or Restart of processes may be required)" ToolTip="Sets environment variable POWERSHELL_TELEMETRY_OPTOUT to 1, see https://go.microsoft.com/fwlink/?linkid=2242006"/> - <Control Id="ContextMenuOpenPowerShell" Type="CheckBox" X="20" Y="140" Width="290" Height="17" Property="ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL" CheckBoxValue="1" Text="Add '$(var.ExplorerContextSubMenuDialogText)' context menus to Explorer"/> - <Control Id="ContextMenuRunPowerShell" Type="CheckBox" X="20" Y="160" Width="290" Height="17" Property="ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL" CheckBoxValue="1" Text="Add '$(var.PowerShellFileContextSubMenuDialogText)' context menu for PowerShell files"/> - <Control Id="LicenseLink" Type="Hyperlink" X="20" Y="190" Width="214" Height="17"> - <Text><![CDATA[<a href="https://github.com/PowerShell/PowerShell/blob/master/LICENSE.txt">The application is distributed under the MIT license.</a>]]></Text> - </Control> - <Control Id="TpnLink" Type="Hyperlink" X="20" Y="210" Width="290" Height="17"> - <Text><![CDATA[<a href="https://github.com/PowerShell/PowerShell/blob/master/ThirdPartyNotices.txt">Please review the ThirdPartyNotices.txt</a>]]></Text> - </Control> - <!-- divider and bottom buttons --> - <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" TabSkip="yes"/> - <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)"/> - <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Cancel="yes" Default="yes" Text="!(loc.WixUINext)"/> - <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)"> - <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish> - </Control> - </Dialog> - </UI> -</Fragment> - -<!-- Microsoft Update Menu Dialog --> -<Fragment> - <UI> - <Dialog Id="MuDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)"> - <!-- Banner --> - <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="yes" Text="!(loc.InstallDirDlgBannerBitmap)"/> - <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="30" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Title}Use Microsoft Update to help keep your computer secure and up to date" TabSkip="yes" /> - <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" TabSkip="yes" /> - <Control Id="Description" Type="Text" X="25" Y="54" Width="280" Height="100" Transparent="yes" NoPrefix="yes" Text="Microsoft Update helps keep your computer secure and up to date for Windows and other Microsoft products, including PowerShell 7. Updates will be delivered based on your current update settings. You can review or change these settings from the Windows Update control panel." TabSkip="yes" /> - <!-- If the checkboxes are defined first, then they are first in the tab order and can be ticked and unticked using the spacebar --> - <Control Id="UseMuCheckBox" Type="CheckBox" X="20" Y="100" Width="290" Height="27" Property="USE_MU" CheckBoxValue="1" Text="Enable updating PowerShell through Microsoft Update or WSUS (recommended)"/> - <Control Id="EnableMuCheckBox" Type="CheckBox" X="20" Y="130" Width="290" Height="17" Property="ENABLE_MU" CheckBoxValue="1" Text="Use Microsoft Update when I check for updates (recommended)"> - </Control> - <Control Id="MuWarningText" Type="Text" X="20" Y="160" Width="290" Height="27" Text="Without Microsoft Update enabled, you will need to use another update solution like WSUS or SCCM in order to receive automatic updates."> - <Condition Action="hide"><![CDATA[ENABLE_MU="1" OR USE_MU<>"1"]]></Condition> - <Condition Action="show"><![CDATA[ENABLE_MU<>"1" AND USE_MU="1"]]></Condition> - </Control> - <Control Id="MuFAQ" Type="Hyperlink" X="20" Y="190" Width="214" Height="17"> - <Text><![CDATA[<a href="https://aka.ms/PowerShell-Microsoft-Update-FAQ">See the Microsoft Update FAQ</a>]]></Text> - </Control> - <Control Id="PrivacyLink" Type="Hyperlink" X="20" Y="210" Width="290" Height="17"> - <Text><![CDATA[<a href="https://aka.ms/PowerShell-Microsoft-Update-Privacy-Statement">Read the Microsoft Update Privacy Statement</a>]]></Text> - </Control> - <!-- divider and bottom buttons --> - <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" TabSkip="yes"/> - <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)"/> - <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Cancel="yes" Default="yes" Text="!(loc.WixUINext)"/> - <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)"> - <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish> - </Control> - </Dialog> - </UI> -</Fragment> -<!-- Customized version of WixUI_InstallDir, which is necessary to add custom dialogs. https://github.com/wixtoolset/wix3/blob/master/src/ext/UIExtension/wixlib/WixUI_InstallDir.wxs --> -<Fragment> - <UI Id="CustomWixUI_InstallDir"> - <!-- - First-time install dialog sequence: - - WixUI_WelcomeDlg - - WixUI_InstallDirDlg - - ExplorerContextMenuDialog - - MuDialog - - WixUI_VerifyReadyDlg - - WixUI_DiskCostDlg - Maintenance dialog sequence: - - WixUI_MaintenanceWelcomeDlg - - WixUI_MaintenanceTypeDlg - - WixUI_InstallDirDlg - - WixUI_VerifyReadyDlg - Patch dialog sequence: - - WixUI_WelcomeDlg - - WixUI_VerifyReadyDlg - --> - - <TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" /> - <TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" /> - <TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" /> - - <Property Id="DefaultUIFont" Value="WixUI_Font_Normal" /> - <Property Id="WixUI_Mode" Value="InstallDir" /> - - <DialogRef Id="BrowseDlg" /> - <DialogRef Id="DiskCostDlg" /> - <DialogRef Id="ErrorDlg" /> - <DialogRef Id="FatalError" /> - <DialogRef Id="FilesInUse" /> - <DialogRef Id="MsiRMFilesInUse" /> - <DialogRef Id="PrepareDlg" /> - <DialogRef Id="ProgressDlg" /> - <DialogRef Id="ResumeDlg" /> - <DialogRef Id="UserExit" /> - <DialogRef Id="ExplorerContextMenuDialog" /> - - - <Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish> - <Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish> - - <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg">NOT Installed</Publish> - <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish> - - <Publish Dialog="ExplorerContextMenuDialog" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish> - <Publish Dialog="ExplorerContextMenuDialog" Control="Next" Event="NewDialog" Value="MuDialog">1</Publish> - <Publish Dialog="MuDialog" Control="Back" Event="NewDialog" Value="ExplorerContextMenuDialog">1</Publish> - <Publish Dialog="MuDialog" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="2">1</Publish> - - <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish> - <Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish> - <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish> - <Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish> - <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish> - <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="ExplorerContextMenuDialog" Order="5">1</Publish> - <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish> - <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish> - - <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MuDialog" Order="1">NOT Installed</Publish> - <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish> - <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">Installed AND PATCH</Publish> - - <Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish> - - <Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish> - <Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish> - <Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish> - - <Property Id="ARPNOMODIFY" Value="1" /> - </UI> - <UIRef Id="WixUI_Common" /> - </Fragment> - -</Wix> diff --git a/assets/wix/WixUIBannerBmp.png b/assets/wix/WixUIBannerBmp.png deleted file mode 100644 index 2a1922c1d7c83c687f6abf001a773d0d5387c095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3855 zcma)9c|278_h&4V$&h^uMUp*~eQB|zWFkw}22ma&+gOG)lzk~YR2W&FhGdLr5=K)D zDip$HEHSdqV940{4Lz^t`|t0MyL|4wuXE4myx-@%&q=ywWe$aiLs(c?pca=*Y*<)W zY0P`QL!8WW!8$9zys!q_m|tMQb{(B%F4%9Mzj~g9r8?vAwmS!N&2{gRV=xN~CvN{> z4c49*XFe1SF})FTJ-|C8%st49<(|8be~7BTSIDt5s_Lp*J62pXEG#?^EKJVZhP%;5 ze6q9qo`LUq@!feb8(p&YHuB~Shxl_hpMHOPN4*E}S4&z>#dqk;b!&=2t#yNSf+zTM zADW2$NX^vxY$+^CX=xGtELtEYnkxGSc+D!#G{P`mYg%-+3$Qb^3-L5+jPBY)uWT*n zPgK?y?%EDSqmgVZGBPsapcWZEK0e)y8z-2nX}%N_6O%~;k0*(ViTUQr{Cs>*RuWB_ zZ-S(%FEL+r$qFYWCdL;Zc0l#!KDcgp-sMaI_pjL5hvbMV%KiG``snDF1*^fWKSFtC z><hbh--9pj8-dC>Y&{r>(MuIt^CxAShIuZ>Ryk=Ed+!#M`Fs%QqlJmS8fuFDf+t(+ zE^1jmZ#eImkUZ#XZ8ZP&R9=MA7bk}jzgER;{A&}RVDM$}+>$zXF^EJ?F>cpe(DG*L zcKxG!SDjfLXi<|_eJ=WUx=h-3nlVc4Z`suzt7+sC^Ga(!Rc;;`DA@yfDRVYP!)E!_ z6MG$wD+6O0Fe^qyhU%ic=>G1`WJlerlX#o5a6WKePuf(ozqrf`3UO{q{WRrl4TKzq zJhv}-5h%1QVXt^Iw&Fg=@?6gGfFYYnE7#ckA=7sBn<Il+)3OyWC7;=?Jhs*ai)DGs z5<E`!d~tr_)F1mJ^JJ-@_ObW(^iB#}Ud*fzPKr2tslEI{6yrpTSB|V!iJ-a<DbY&B z#5TxCMt(|0MqNUHUxQAaUo8Khhqk|8slA3#0m(DClws6}-(jkmGaU8~%<vc^V*62s z1M;}u5O}T=%JmipqKSw4`BHnO7MuQ82sK0rcRd6?eO%@<H+5+}B<jOT#vJ7<5@L3M zkI;z2gSWg!LZW~sCqM8t8l>NLh{}N`cD)V)GnAd-+7(29ZhqizMVwMBJ0cxIRhGrc z3e+sM#pU+3<*o^)?$bwHG2NAYE@xyOdiOZ)?cHyChYFACroUTTXzQ@c8}C86V%WC4 zkK@Q4L~JdX!R-tmNH|2*5CY^_QIbU<<b^2CPoBNkdK-@vqU{Ua0NAO08r~^er=hMN zXLF8eZVLFSIrq<K==Sf3Gga$)L74Y!$R|gD;KxQj!aX#Us|N?dy#PWjzZBw#D)8h$ zyc7a_OcSjX&e;Y@$%Miy0{YG1ZU4AOSwj5Z&r9F4-?@EJB@p&;pWnqr=L$dEuU`pc z{%Fr)yRUG^iO>@!Ij0v_ZyGiPLK?jRfso#bM07Ezp;Z6~RK$7O!P~+?WI0Yo-Aod& zJp7?(FPP$)xb<&9ISdIz!;BB57s$eZ3ZCfi^v?yPTob))QTy(i-qI|WZKs>>VZW8q zIzC|Y=QDX+9tfmw&4CZJF}PAFC;}n}+YCdfH#Bi=rgHAzw&YjIDM*Ov0aP6Fkq_`t zUNAeNNCmfARI|Owvkf1JDU0E+GuoaxbvaMZ097!~Lk*GyRJ`F5m9C*rZ=4pg(8H1X zRfT}^T~t9iNPk2C@GRkf;%t+WkuDLSvY;S%kN|?)umLZ@Z!Q>+zO5Pp?9%H{_R|ej z*t8+P`VBOoFC7IMsFlO-+wo9C;E4V5q&N=5D+u+LEUsB*qI<NOPyU-kfYN{F8n>rn z#89*i$u05b>yY_e%GlUQ)vnLap_Q$jcC3%4RWeu_tuZLUG>3=yIjiCM0QFI#l;&~a zJn6x7rRY}Y<bSuV>skeAN+=xt1@$9;thq)2Ow+#7v76p1m0gB=NgWWOq2mddnYh~@ z<1sUCetSf#Wp>64y&KpFrWivf(Ab06>5j}w!AURWv68?AHT*7gtC`Kb&l5C|u6@4( z<ScE_FgmVKFQP9*v4hWFyzMC~V1SYoDvaMV(pY~+@!w}-`>c`S+WSCy!ckx|%ufkt zNC29eub+I2{Zz;+hN)xgzpj732;^KS=zx-}6(K{Y#|QwWxRc0}cO*ew5oey3(()sR zq!Za1emD>|AhTag^B1O(yc_KJt<vywaM-FO+GCM~Hkh_QYUKU0Y;Tj%Yy2j%nvIQ2 z9uiW<$w#v38;F<K!80Iv56<REHItNDDI`PJl>Ph*(lC5FnIyfS#YXNQ`URzWnZc*$ zi2d&n91>_7b%E8x0}=u~+XK^ch6^#&Ek#@?6T@+Z&C>A4#PshdNl#7&z*Xnd5$=cV z7G0KE@Hw_G4NtNP2<*d*@0v!aN@R638#%i3r6F!79&>{LR9?}ZunfzHtX-UQ_)MUq z?s~&--$1G05fKTP)U+S+q!E<lFD|OJJB8j5J+fInf#$rh&tYB1+)a;RTvXPEKHi_E z0V4a+mZ8)~tSH)8N+lD<tcewev6PhD$UaK+Kws1S;pM&$(o0K%l+rpLDxFCX-AH8@ zueUmPb|0$#=jP7%URO99SsXpFK*EFZfoh1*h9f|b8Y7u3)gm-<W$InMnv0oLUp^}o zCMJM|CC?Mnm)IGJd985z91=U9c|?Cd2PSPd(P_8!$5!vsmbs{;(S4Gq5z+Gkz!U*M z5ddGUU8pKUbj=0)I>J}F+Nq&*T!G=nq(bv2LzH3E<SB#N)nD`VCt9oycG^dB`#CD_ zw#<p&mDE3m8>w_%c|6PIw?+W;aH|Ahl-Q*l(87?cDsTHbUz0V`UVc*~D*SS3Sa`BF zt}OzD1eW+_wmUJK*38%)NU3fg{qQ+*eL^acQH$7s!szcsoVoqxpI;RMuF{mM_hch3 zJa=oeDroyuWtdR*n^|7_E$GLZuZO7N<#FcBig}`#k`?&kYj}Pg2F1TG_g0}lO{xM@ z9DXhd2JC;NL_^V<ynB&q4A+>gtG`_`_l{x*&|$`AtC<N$3hWhY5rgrVhhz{km4|v7 z1|QS$(-FU|)?8iFDoZkiW*;!|mBZ-JV(*a9U*XB!Lvrl+3UA^2+f@yHl*!Qd?|8=| z$FG(SV9m0bF_~NvtPs(+EEEg_GwyIpxN|a~ihnKE1>{A(q~8gIvB@3e$jleJXp2i2 zd7Pm<=^{6b&P&ColLKek)mL>Qb~cuLIzG%dTxt(Eq;5bOO`%DmHEcMT$cDhjZgDcw zw8e{dhUXV&%~O!#+y@w&U{pbpc~a89mjp*r3haj-R*r2*ZEmI=)(7RFA28<Bj7_@p zI}<IG#C|VjKPmZbLGKpDf{;&Dm>JB|yai6H8f4;e@c`lOqTq9V!>f2AV$B1|Mve(j zULawgRSSL?+bW8D*ti>n1&0}Jif1ZKCCrd=;xPcSx{@em1k!dwRsL;%cB0*fa<|10 z8h;SC;t}biP;Nh%8s5ObYiERaqT@YraAIM#3!HH6-7-1xYr*oPcuY7O@~J%TZ90_q zXKY0O)!|<FL63&%j<diw)wby<EC>L7%S+k$bB^KTq!{ZxTes-wo$%$q!yb>|Seb1G z9$8&=_TNy+aNzO3o)vz~FPB;LJ$;b!y&)9=6%tZWU#diq=!m6`Dv^eWadE2tk;b*n zc6#s)dK&GLqQ7a#tI++59T|Q(M;<pq0NjlT0DfEn@8+j~TP^%N7B#j`uB*d*{KK*p zNA2%*R9`$1Z<V~o&fp$iAqxL?(NL?*v7fG5E2^a#wRkyfup{pFx@`B>s7)kPZU6Ya z3|orJ9Xm~<wlE5eTNyF^i1E+UVJ|{Q`JL4eaJcX6AjkUpLpq*`#+3s!I!EKC(*g#W z#R`^Ou@8yy#wV*)n~2#f##(oy*~-se;mdN1y+d)MMw!S>7rpRs`u>daF{cS%6S1qv z4Errc@v2Vr$ME<6^E+zVO^B%%hz(0w=C(+2(aoa`i>IFVZ_8#v=J+Z=<T`InQoXmP z0$B3hs6Qd--n=u6fX)1Yv>6_o3TS#@w9<1r5El1`$dkoiCOR_+r`9~k@tBYTof5YN z*PWe0iG<D|@x^m4{U?s&I<qDQAK96a2jKRLeMYtSXBR=SLkoR3CBLf)t%Vr=DauEU zd21^PV*-HKr={%43C`Dhn<eNi+^T!c-)n@2*~y55p0XK2wN=9HI5m8c@NcDzZONg> zR!7g?CDXOMD+gnrxbv}|rwL2(rA3>k1yh(RnQ%bD%ss<yFjMI5;b0z^I(AU`Ox2*W zuVWg=&B5fxDgDP!jr$-}o03YU8*pB~@MMCB{(51{$X^oVZm;`vk^fw5Wb~WgM=_Jp zgO_^3L4|+ir0j7Fgg%(H4QjZ?fj3gXf}E)&JcxXU_69`8h&W53`8(<x-ktk)`Q0KV z<qvDR$-D;3R9F;<+_*2NGD^6WN@7=~J<2tyPFV@pTS;Whl6L2zFs`ZDf`Ez)wZ+-U zr2T;fQ#TAY*q&szDojyt{aE7>?dyOeVh<hgmGcu=-@pqXvXSj<;xaNMM6wc*^==0O zZ^4i<NZ|05K4(25Q}<H!+-Npp(h8lB|4O$Vo1gSLS*%?-z|721K;yE+V>b;+pGUoq zolbKoV8u^N+qrvK$z{G#di>Ne{$U4y3r6IaYTqLu+oi>_<9vw6$zuK<KC#@`#HUG5 zk7Ok-m4&BfB$Dd5tVQ*WCZ5Z9@_+BaLjnjfpX$zbG$nlCJg8J>>SZhD8+&M1V3#%X znS<4Q8TqEAd8`f<F*NmqI7@D<@9a_!<h^-NcK^TgTYJQxp%3c69y3#6Zy6fKZNQvY OSu9MgOt2T=vHu4m33(F$ diff --git a/assets/wix/WixUIDialogBmp.png b/assets/wix/WixUIDialogBmp.png deleted file mode 100644 index 57cf3734c176908bc7ae2435a0e31711ae210ebe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30804 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 RaOp3Vo{sSikf(|M{{S(tW9a|@ diff --git a/assets/wix/WixUIInfoIco.png b/assets/wix/WixUIInfoIco.png deleted file mode 100644 index cb705d5f33f1a3451203f97d0228031368355dc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638 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 diff --git a/assets/wix/bundle.wxs b/assets/wix/bundle.wxs deleted file mode 100644 index a2d93b47099..00000000000 --- a/assets/wix/bundle.wxs +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"> - <!-- UpgradeCode GUID MUST REMAIN SAME THROUGHOUT ALL VERSIONS, otherwise, updates won't occur. --> - <?if $(sys.BUILDARCH)=x64?> - <?define UpgradeCodePreview = "3C90221B-D500-43C6-A4A6-0BE6C2C1B317"?> - <?define UpgradeCodeRelease = "7A804CBB-648E-4276-9A58-081862DB1B99"?> - <?if $(var.IsPreview)=True?> - <?define UpgradeCode = $(var.UpgradeCodePreview)?> - <?else?> - <?define UpgradeCode = $(var.UpgradeCodeRelease)?> - <?endif?> - <?elseif $(sys.BUILDARCH) = "ARM64"?> - <?define ExplorerContextMenuDialogText = "&$(var.ProductName) $(var.SimpleProductVersion) ($(sys.BUILDARCH))"?> - <?define UpgradeCodePreview = "499e9123-48aa-41df-aa20-6f4d28b54722"?> - <?define UpgradeCodeRelease = "4cc0e36a-17db-4c84-b4f4-560a11e7ddb6"?> - <?if $(var.IsPreview)=True?> - <?define UpgradeCode = $(var.UpgradeCodePreview)?> - <?else?> - <?define UpgradeCode = $(var.UpgradeCodeRelease)?> - <?endif?> - <?else?> - <?define UpgradeCodePreview = "4A699A9C-E904-4024-BCD2-44E098A8C6BD"?> - <?define UpgradeCodeRelease = "ED46CB02-64B3-43FD-A63E-6CF269D8C21C"?> - <?if $(var.IsPreview)=True?> - <?define UpgradeCode = $(var.UpgradeCodePreview)?> - <?else?> - <?define UpgradeCode = $(var.UpgradeCodeRelease)?> - <?endif?> - <?endif?> - - <Bundle Name="PowerShell $(var.WindowsVersion)-$(sys.BUILDARCH)" Version="$(var.WindowsVersion)" Manufacturer="Microsoft Corporation" UpgradeCode="$(var.UpgradeCode)"> - <!-- See https://wixtoolset.org/documentation/manual/v3/bundle/wixstdba/ for a list of WiX standard bootstrapper types. --> - <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense"> - <bal:WixStandardBootstrapperApplication LicenseFile="assets\wix\ExeLicense.rtf" LogoFile="assets\ps_black_32x32.ico" /> - </BootstrapperApplicationRef> - <Chain> - <MsiPackage SourceFile="$(var.TargetPath)" Compressed="yes" /> - </Chain> - </Bundle> -</Wix> diff --git a/build.psm1 b/build.psm1 index 737b3e6182f..d353eb251e5 100644 --- a/build.psm1 +++ b/build.psm1 @@ -3003,11 +3003,6 @@ function Start-PSBootstrap { $psInstallFile = [System.IO.Path]::Combine($PSScriptRoot, "tools", "install-powershell.ps1") & $psInstallFile -AddToPath } - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { - Import-Module "$PSScriptRoot\tools\wix\wix.psm1" - $isArm64 = "$env:RUNTIME" -eq 'arm64' - Install-Wix -arm64:$isArm64 - } Write-LogGroupEnd -Title "Install Windows Dependencies" } diff --git a/docs/maintainers/releasing.md b/docs/maintainers/releasing.md index 3562962e68f..ccb4e5529d7 100644 --- a/docs/maintainers/releasing.md +++ b/docs/maintainers/releasing.md @@ -57,17 +57,13 @@ It **requires** that PowerShell Core has been built via `Start-PSBuild` from the #### Windows -The `Start-PSPackage` function delegates to `New-MSIPackage` which creates a Windows Installer Package of PowerShell. +`Start-PSPackage` supports creating ZIP and MSIX packages for Windows. +When called without `-Type` on Windows, it defaults to creating both ZIP and MSIX packages. The packages *must* be published in release mode, so make sure `-Configuration Release` is specified when running `Start-PSBuild`. -It uses the Windows Installer XML Toolset (WiX) to generate a MSI package, -which copies the output of the published PowerShell files to a version-specific folder in Program Files, -and installs a shortcut in the Start Menu. -It can be uninstalled through `Programs and Features`. - Note that PowerShell is always self-contained, thus using it does not require installing it. -The output of `Start-PSBuild` includes a `powershell.exe` executable which can simply be launched. +The output of `Start-PSBuild` includes a `pwsh.exe` executable which can simply be launched. #### Linux / macOS @@ -169,8 +165,8 @@ Start-PSBuild -Clean -CrossGen -PSModuleRestore -Runtime win7-x64 -Configuration ```powershell # Create packages for v6.0.0-beta.1 release targeting Windows universal package. # 'win7-x64' / 'win7-x86' should be used for -WindowsRuntime. -Start-PSPackage -Type msi -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' Start-PSPackage -Type zip -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' +Start-PSPackage -Type msix -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' ``` ## NuGet Packages diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj index e6efeac10f0..dddbb915eca 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -33,7 +33,7 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </Content> - <Content Include="..\..\LICENSE.txt;..\..\ThirdPartyNotices.txt;..\powershell-native\Install-PowerShellRemoting.ps1;..\PowerShell.Core.Instrumentation\PowerShell.Core.Instrumentation.man;..\PowerShell.Core.Instrumentation\RegisterManifest.ps1;..\..\assets\MicrosoftUpdate\RegisterMicrosoftUpdate.ps1;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.admx;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.adml;..\..\assets\GroupPolicy\InstallPSCorePolicyDefinitions.ps1"> + <Content Include="..\..\LICENSE.txt;..\..\ThirdPartyNotices.txt;..\powershell-native\Install-PowerShellRemoting.ps1;..\PowerShell.Core.Instrumentation\PowerShell.Core.Instrumentation.man;..\PowerShell.Core.Instrumentation\RegisterManifest.ps1;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.admx;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.adml;..\..\assets\GroupPolicy\InstallPSCorePolicyDefinitions.ps1"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </Content> diff --git a/test/perf/benchmarks/assets/compiler.test.ps1 b/test/perf/benchmarks/assets/compiler.test.ps1 index 5105ae9b408..be731373036 100644 --- a/test/perf/benchmarks/assets/compiler.test.ps1 +++ b/test/perf/benchmarks/assets/compiler.test.ps1 @@ -2238,26 +2238,6 @@ function Start-PSPackage { } } } - "msi" { - $TargetArchitecture = "x64" - if ($Runtime -match "-x86") { - $TargetArchitecture = "x86" - } - Write-Verbose "TargetArchitecture = $TargetArchitecture" -Verbose - - $Arguments = @{ - ProductNameSuffix = $NameSuffix - ProductSourcePath = $Source - ProductVersion = $Version - AssetsPath = "$RepoRoot\assets" - ProductTargetArchitecture = $TargetArchitecture - Force = $Force - } - - if ($PSCmdlet.ShouldProcess("Create MSI Package")) { - New-MSIPackage @Arguments - } - } "msix" { $Arguments = @{ ProductNameSuffix = $NameSuffix diff --git a/test/powershell/Installer/WindowsInstaller.Tests.ps1 b/test/powershell/Installer/WindowsInstaller.Tests.ps1 deleted file mode 100644 index 66bd08e74f5..00000000000 --- a/test/powershell/Installer/WindowsInstaller.Tests.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -Describe "Windows Installer" -Tags "Scenario" { - - BeforeAll { - $skipTest = -not $IsWindows - $preRequisitesLink = 'https://aka.ms/pscore6-prereq' - $linkCheckTestCases = @( - @{ Name = "Universal C Runtime"; Url = $preRequisitesLink } - @{ Name = "WMF 4.0"; Url = "https://www.microsoft.com/download/details.aspx?id=40855" } - @{ Name = "WMF 5.0"; Url = "https://www.microsoft.com/download/details.aspx?id=50395" } - @{ Name = "WMF 5.1"; Url = "https://www.microsoft.com/download/details.aspx?id=54616" } - ) - } - - It "WiX (Windows Installer XML) file contains pre-requisites link $preRequisitesLink" -Skip:$skipTest { - $wixProductFile = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\assets\wix\Product.wxs" - (Get-Content $wixProductFile -Raw).Contains($preRequisitesLink) | Should -BeTrue - } - - ## Running 'Invoke-WebRequest' with WMF download URLs has been failing intermittently, - ## because sometimes the URLs lead to a 'this download is no longer available' page. - ## We use a retry logic here. Retry for 5 times with 1 second interval. - # It "Pre-Requisistes link for '<Name>' is reachable: <url>" -TestCases $linkCheckTestCases -Skip:$skipTest { - It "Pre-Requisistes link for '<Name>' is reachable: <url>" -TestCases $linkCheckTestCases -Pending { - param ($Url) - - foreach ($i in 1..5) { - try { - $result = Invoke-WebRequest $Url -UseBasicParsing - break; - } catch { - Start-Sleep -Seconds 1 - } - } - - $result | Should -Not -Be $null - } -} diff --git a/tools/UpdateDotnetRuntime.ps1 b/tools/UpdateDotnetRuntime.ps1 index 339153bcf0c..f62a16cfe14 100644 --- a/tools/UpdateDotnetRuntime.ps1 +++ b/tools/UpdateDotnetRuntime.ps1 @@ -9,9 +9,6 @@ param ( [Parameter()] [switch]$UseNuGetOrg, - [Parameter()] - [switch]$UpdateMSIPackaging, - [Parameter()] [string]$RuntimeSourceFeed, @@ -366,31 +363,6 @@ if ($dotnetUpdate.ShouldUpdate) { Write-Verbose -Message "Updating project files completed." -Verbose - if ($UpdateMSIPackaging) { - if (-not $environment.IsWindows) { - throw "UpdateMSIPackaging can only be done on Windows" - } - - Import-Module "$PSScriptRoot/../build.psm1" -Force - Import-Module "$PSScriptRoot/packaging" -Force - Start-PSBootstrap -Package - Start-PSBuild -Clean -Configuration Release -InteractiveAuth:$InteractiveAuth - - $publishPath = Split-Path (Get-PSOutput) - Remove-Item -Path "$publishPath\*.pdb" - - try { - Start-PSPackage -Type msi -SkipReleaseChecks -InformationVariable wxsData - } catch { - if ($_.Exception.Message -like "Current files to not match *") { - Copy-Item -Path $($wxsData.MessageData.NewFile) -Destination ($wxsData.MessageData.FilesWxsPath) - Write-Verbose -Message "Updating files.wxs file completed." -Verbose - } else { - throw $_ - } - } - } - Update-DevContainer } else { diff --git a/tools/ci.psm1 b/tools/ci.psm1 index b9d150ceacd..33700ac9024 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -241,7 +241,7 @@ function Install-CIPester Write-Verbose "Checking for Pester module (required: $MinimumVersion - $MaximumVersion)" -Verbose # Check if a compatible version of Pester is already installed - $installedPester = Get-Module -Name Pester -ListAvailable | + $installedPester = Get-Module -Name Pester -ListAvailable | Where-Object { $_.Version -ge $MinimumVersion -and $_.Version -le $MaximumVersion } | Sort-Object -Property Version -Descending | Select-Object -First 1 @@ -616,20 +616,9 @@ function Invoke-CIFinish Restore-PSOptions -PSOptionsPath "${buildFolder}/psoptions.json" $preReleaseVersion = $env:CI_FINISH_RELASETAG - # Build packages $preReleaseVersion = "$previewPrefix-$previewLabel.$prereleaseIteration" - switch -regex ($Runtime){ - default { - $runPackageTest = $true - $packageTypes = 'msi', 'zip', 'zip-pdb', 'msix' - } - 'win-arm.*' { - $runPackageTest = $false - $packageTypes = 'msi', 'zip', 'zip-pdb', 'msix' - } - } + # Build packages + $packageTypes = 'zip', 'zip-pdb', 'msix' - Import-Module "$PSScriptRoot\wix\wix.psm1" - Install-Wix -arm64:$true $packages = Start-PSPackage -Type $packageTypes -ReleaseTag $preReleaseVersion -SkipReleaseChecks -WindowsRuntime $Runtime foreach ($package in $packages) { @@ -641,40 +630,9 @@ function Invoke-CIFinish if ($package -is [string]) { $null = $artifacts.Add($package) - } elseif ($package -is [pscustomobject] -and $package.psobject.Properties['msi']) { - $null = $artifacts.Add($package.msi) - $null = $artifacts.Add($package.wixpdb) } } - if ($runPackageTest) { - # the packaging tests find the MSI package using env:PSMsiX64Path - $env:PSMsiX64Path = $artifacts | Where-Object { $_.EndsWith(".msi")} - $architechture = $Runtime.Split('-')[1] - $exePath = New-ExePackage -ProductVersion ($preReleaseVersion -replace '^v') -ProductTargetArchitecture $architechture -MsiLocationPath $env:PSMsiX64Path - Write-Verbose "exe Path: $exePath" -Verbose - $artifacts.Add($exePath) - $env:PSExePath = $exePath - $env:PSMsiChannel = $Channel - $env:PSMsiRuntime = $Runtime - - # Install the latest Pester and import it - $maximumPesterVersion = '4.99' - Install-CIPester -MinimumVersion '4.0.0' -MaximumVersion $maximumPesterVersion -Force - Import-Module Pester -Force -MaximumVersion $maximumPesterVersion - - $testResultPath = Join-Path -Path $env:TEMP -ChildPath "win-package-$channel-$runtime.xml" - - # start the packaging tests and get the results - $packagingTestResult = Invoke-Pester -Script (Join-Path $repoRoot '.\test\packaging\windows\') -PassThru -OutputFormat NUnitXml -OutputFile $testResultPath - - Publish-TestResults -Title "win-package-$channel-$runtime" -Path $testResultPath - - # fail the CI job if the tests failed, or nothing passed - if (-not $packagingTestResult -is [pscustomobject] -or $packagingTestResult.FailedCount -ne 0 -or $packagingTestResult.PassedCount -eq 0) { - throw "Packaging tests failed ($($packagingTestResult.FailedCount) failed/$($packagingTestResult.PassedCount) passed)" - } - } } } catch { Get-Error -InputObject $_ @@ -932,12 +890,12 @@ function New-LinuxPackage } else { "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" } - + # Ensure artifacts directory exists if (-not (Test-Path $artifactsDir)) { New-Item -ItemType Directory -Path $artifactsDir -Force | Out-Null } - + Write-Log -message "Artifacts directory: $artifactsDir" Copy-Item $packageObj.FullName -Destination $artifactsDir -Force } @@ -950,7 +908,7 @@ function New-LinuxPackage } else { "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" } - + # Create and package Raspbian .tgz # Build must be clean for Raspbian Start-PSBuild -PSModuleRestore -Clean -Runtime linux-arm -Configuration 'Release' @@ -1062,13 +1020,13 @@ Function Test-MergeConflictMarker [string]$OutputPath, [string]$SummaryPath ) - + # Output results to GitHub Actions if ($OutputPath) { "files-checked=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8 "conflicts-found=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8 } - + # Create GitHub Actions job summary if ($SummaryPath) { $summaryContent = @" @@ -1097,11 +1055,11 @@ $Message # Filter out *.cs files from merge conflict checking $filesToCheck = @($File | Where-Object { $_ -notlike "*.cs" }) $filteredCount = $File.Count - $filesToCheck.Count - + if ($filteredCount -gt 0) { Write-Host "Filtered out $filteredCount *.cs file(s) from merge conflict checking" -ForegroundColor Yellow } - + if ($filesToCheck.Count -eq 0) { Write-Host "No files to check after filtering (all files were *.cs)" -ForegroundColor Yellow Write-NoFilesOutput -Message "All $filteredCount file(s) were filtered out (*.cs files are excluded from merge conflict checking)." -OutputPath $OutputPath -SummaryPath $SummaryPath @@ -1135,14 +1093,14 @@ $Message } $filesChecked++ - + # Get relative path for display $relativePath = if ($WorkspacePath -and $filePath.StartsWith($WorkspacePath)) { $filePath.Substring($WorkspacePath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) } else { $filePath } - + Write-Host " Checking: $relativePath" -ForegroundColor Gray # Search for conflict markers using Select-String diff --git a/tools/packaging/boms/windows.json b/tools/packaging/boms/windows.json index 8d900902bc2..8d8154e7445 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -4616,11 +4616,6 @@ "FileType": "Product", "Architecture": null }, - { - "Pattern": "RegisterMicrosoftUpdate.ps1", - "FileType": "Product", - "Architecture": null - }, { "Pattern": "System.Management.Automation.dll", "FileType": "Product", diff --git a/tools/packaging/packaging.psd1 b/tools/packaging/packaging.psd1 index 0053428a481..b72c14b80f1 100644 --- a/tools/packaging/packaging.psd1 +++ b/tools/packaging/packaging.psd1 @@ -7,13 +7,10 @@ PowerShellVersion = "5.0" CmdletsToExport = @() FunctionsToExport = @( - 'Compress-ExePackageEngine' - 'Expand-ExePackageEngine' 'Expand-PSSignedBuild' 'Invoke-AzDevOpsLinuxPackageBuild' 'Invoke-AzDevOpsLinuxPackageCreation' 'New-DotnetSdkContainerFxdPackage' - 'New-ExePackage' 'Start-PrepForGlobalToolNupkg' 'New-GlobalToolNupkgSource' 'New-GlobalToolNupkgFromSource' diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 361f82769f0..a551a483c8d 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -52,7 +52,7 @@ function Start-PSPackage { [string]$Name = "powershell", # Ubuntu, CentOS, Fedora, macOS, and Windows packages are supported - [ValidateSet("msix", "deb", "osxpkg", "rpm", "rpm-fxdependent", "rpm-fxdependent-arm64", "msi", "zip", "zip-pdb", "tar", "tar-arm", "tar-arm64", "tar-alpine", "fxdependent", "fxdependent-win-desktop", "min-size", "tar-alpine-fxdependent")] + [ValidateSet("msix", "deb", "osxpkg", "rpm", "rpm-fxdependent", "rpm-fxdependent-arm64", "zip", "zip-pdb", "tar", "tar-arm", "tar-arm64", "tar-alpine", "fxdependent", "fxdependent-win-desktop", "min-size", "tar-alpine-fxdependent")] [string[]]$Type, # Generate windows downlevel package @@ -346,7 +346,7 @@ function Start-PSPackage { } elseif ($Environment.IsMacOS) { "osxpkg", "tar" } elseif ($Environment.IsWindows) { - "msi", "msix" + "zip", "msix" } Write-Warning "-Type was not specified, continuing with $Type!" } @@ -493,34 +493,6 @@ function Start-PSPackage { } } } - "msi" { - $TargetArchitecture = "x64" - $r2rArchitecture = "amd64" - if ($Runtime -match "-x86") { - $TargetArchitecture = "x86" - $r2rArchitecture = "i386" - } - elseif ($Runtime -match "-arm64") - { - $TargetArchitecture = "arm64" - $r2rArchitecture = "arm64" - } - - Write-Verbose "TargetArchitecture = $TargetArchitecture" -Verbose - - $Arguments = @{ - ProductNameSuffix = $NameSuffix - ProductSourcePath = $Source - ProductVersion = $Version - AssetsPath = "$RepoRoot\assets" - ProductTargetArchitecture = $TargetArchitecture - Force = $Force - } - - if ($PSCmdlet.ShouldProcess("Create MSI Package")) { - New-MSIPackage @Arguments - } - } "msix" { $Arguments = @{ ProductNameSuffix = $NameSuffix @@ -3769,443 +3741,6 @@ function Get-NugetSemanticVersion $packageSemanticVersion } -# Get the paths to various WiX tools -function Get-WixPath -{ - [CmdletBinding()] - param ( - [bool] $IsProductArchitectureArm = $false - ) - - $wixToolsetBinPath = $IsProductArchitectureArm ? "${env:ProgramFiles(x86)}\Arm Support WiX Toolset *\bin" : "${env:ProgramFiles(x86)}\WiX Toolset *\bin" - - Write-Verbose -Verbose "Ensure Wix Toolset is present on the machine @ $wixToolsetBinPath" - if (-not (Test-Path $wixToolsetBinPath)) - { - if (!$IsProductArchitectureArm) - { - throw "The latest version of Wix Toolset 3.11 is required to create MSI package. Please install it from https://github.com/wixtoolset/wix3/releases" - } - else { - throw "The latest version of Wix Toolset 3.14 is required to create MSI package for arm. Please install it from https://aka.ms/ps-wix-3-14-zip" - } - } - - ## Get the latest if multiple versions exist. - $wixToolsetBinPath = (Get-ChildItem $wixToolsetBinPath).FullName | Sort-Object -Descending | Select-Object -First 1 - - Write-Verbose "Initialize Wix executables..." - $wixHeatExePath = Join-Path $wixToolsetBinPath "heat.exe" - $wixMeltExePath = Join-Path $wixToolsetBinPath "melt.exe" - $wixTorchExePath = Join-Path $wixToolsetBinPath "torch.exe" - $wixPyroExePath = Join-Path $wixToolsetBinPath "pyro.exe" - $wixCandleExePath = Join-Path $wixToolsetBinPath "Candle.exe" - $wixLightExePath = Join-Path $wixToolsetBinPath "Light.exe" - $wixInsigniaExePath = Join-Path $wixToolsetBinPath "Insignia.exe" - - return [PSCustomObject] @{ - WixHeatExePath = $wixHeatExePath - WixMeltExePath = $wixMeltExePath - WixTorchExePath = $wixTorchExePath - WixPyroExePath = $wixPyroExePath - WixCandleExePath = $wixCandleExePath - WixLightExePath = $wixLightExePath - WixInsigniaExePath = $wixInsigniaExePath - } -} - -<# - .Synopsis - Creates a Windows installer MSI package and assumes that the binaries are already built using 'Start-PSBuild'. - This only works on a Windows machine due to the usage of WiX. - .EXAMPLE - # This example shows how to produce a Debug-x64 installer for development purposes. - cd $RootPathOfPowerShellRepo - Import-Module .\build.psm1; Import-Module .\tools\packaging\packaging.psm1 - New-MSIPackage -Verbose -ProductSourcePath '.\src\powershell-win-core\bin\Debug\net8.0\win7-x64\publish' -ProductTargetArchitecture x64 -ProductVersion '1.2.3' -#> -function New-MSIPackage -{ - [CmdletBinding()] - param ( - - # Name of the Product - [ValidateNotNullOrEmpty()] - [string] $ProductName = 'PowerShell', - - # Suffix of the Name - [string] $ProductNameSuffix, - - # Version of the Product - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] $ProductVersion, - - # Source Path to the Product Files - required to package the contents into an MSI - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] $ProductSourcePath, - - # File describing the MSI Package creation semantics - [ValidateNotNullOrEmpty()] - [ValidateScript( {Test-Path $_})] - [string] $ProductWxsPath = "$RepoRoot\assets\wix\Product.wxs", - - # File describing the MSI Package creation semantics - [ValidateNotNullOrEmpty()] - [ValidateScript({Test-Path $_})] - [string] $BundleWxsPath = "$RepoRoot\assets\wix\bundle.wxs", - - # Path to Assets folder containing artifacts such as icons, images - [ValidateNotNullOrEmpty()] - [ValidateScript( {Test-Path $_})] - [string] $AssetsPath = "$RepoRoot\assets", - - # Architecture to use when creating the MSI - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture, - - # Force overwrite of package - [Switch] $Force, - - [string] $CurrentLocation = (Get-Location) - ) - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $windowsNames = Get-WindowsNames -ProductName $ProductName -ProductNameSuffix $ProductNameSuffix -ProductVersion $ProductVersion - $productSemanticVersionWithName = $windowsNames.ProductSemanticVersionWithName - $ProductSemanticVersion = $windowsNames.ProductSemanticVersion - $packageName = $windowsNames.PackageName - $ProductVersion = $windowsNames.ProductVersion - Write-Verbose "Create MSI for Product $productSemanticVersionWithName" -Verbose - Write-Verbose "ProductSemanticVersion = $productSemanticVersion" -Verbose - Write-Verbose "packageName = $packageName" -Verbose - Write-Verbose "ProductVersion = $ProductVersion" -Verbose - - $simpleProductVersion = [string]([Version]$ProductVersion).Major - $isPreview = Test-IsPreview -Version $ProductSemanticVersion - if ($isPreview) - { - $simpleProductVersion += '-preview' - } - - $staging = "$PSScriptRoot/staging" - New-StagingFolder -StagingPath $staging -PackageSourcePath $ProductSourcePath - - $assetsInSourcePath = Join-Path $staging 'assets' - - New-Item $assetsInSourcePath -type directory -Force | Write-Verbose - - Write-Verbose "Place dependencies such as icons to $assetsInSourcePath" - Copy-Item "$AssetsPath\*.ico" $assetsInSourcePath -Force - - - - $fileArchitecture = 'amd64' - $ProductProgFilesDir = "ProgramFiles64Folder" - if ($ProductTargetArchitecture -eq "x86") - { - $fileArchitecture = 'x86' - $ProductProgFilesDir = "ProgramFilesFolder" - } - elseif ($ProductTargetArchitecture -eq "arm64") - { - $fileArchitecture = 'arm64' - $ProductProgFilesDir = "ProgramFiles64Folder" - } - - $wixFragmentPath = Join-Path $env:Temp "Fragment.wxs" - - # cleanup any garbage on the system - Remove-Item -ErrorAction SilentlyContinue $wixFragmentPath -Force - - $msiLocationPath = Join-Path $CurrentLocation "$packageName.msi" - $msiPdbLocationPath = Join-Path $CurrentLocation "$packageName.wixpdb" - - if (!$Force.IsPresent -and (Test-Path -Path $msiLocationPath)) { - Write-Error -Message "Package already exists, use -Force to overwrite, path: $msiLocationPath" -ErrorAction Stop - } - - Write-Log "Generating wxs file manifest..." - $arguments = @{ - IsPreview = $isPreview - ProductSourcePath = $staging - ProductName = $ProductName - ProductVersion = $ProductVersion - SimpleProductVersion = $simpleProductVersion - ProductSemanticVersion = $ProductSemanticVersion - ProductVersionWithName = $productVersionWithName - ProductProgFilesDir = $ProductProgFilesDir - FileArchitecture = $fileArchitecture - } - - $buildArguments = New-MsiArgsArray -Argument $arguments - - Test-Bom -Path $staging -BomName windows -Architecture $ProductTargetArchitecture -Verbose - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixHeatExePath dir $staging -dr VersionFolder -cg ApplicationFiles -ag -sfrag -srd -scom -sreg -out $wixFragmentPath -var var.ProductSourcePath $buildArguments -v} - - Send-AzdoFile -Path $wixFragmentPath - - $wixObjFragmentPath = Join-Path $env:Temp "Fragment.wixobj" - - # cleanup any garbage on the system - Remove-Item -ErrorAction SilentlyContinue $wixObjFragmentPath -Force - - Start-MsiBuild -WxsFile $ProductWxsPath, $wixFragmentPath -ProductTargetArchitecture $ProductTargetArchitecture -Argument $arguments -MsiLocationPath $msiLocationPath -MsiPdbLocationPath $msiPdbLocationPath - - Remove-Item -ErrorAction SilentlyContinue $wixFragmentPath -Force - - if ((Test-Path $msiLocationPath) -and (Test-Path $msiPdbLocationPath)) - { - Write-Verbose "You can find the WixPdb @ $msiPdbLocationPath" -Verbose - Write-Verbose "You can find the MSI @ $msiLocationPath" -Verbose - [pscustomobject]@{ - msi=$msiLocationPath - wixpdb=$msiPdbLocationPath - } - } - else - { - $errorMessage = "Failed to create $msiLocationPath" - throw $errorMessage - } -} - -function Get-WindowsNames { - param( - # Name of the Product - [ValidateNotNullOrEmpty()] - [string] $ProductName = 'PowerShell', - - # Suffix of the Name - [string] $ProductNameSuffix, - - # Version of the Product - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] $ProductVersion - ) - - Write-Verbose -Message "Getting Windows Names for ProductName: $ProductName; ProductNameSuffix: $ProductNameSuffix; ProductVersion: $ProductVersion" -Verbose - - $ProductSemanticVersion = Get-PackageSemanticVersion -Version $ProductVersion - $ProductVersion = Get-PackageVersionAsMajorMinorBuildRevision -Version $ProductVersion -IncrementBuildNumber - - $productVersionWithName = $ProductName + '_' + $ProductVersion - $productSemanticVersionWithName = $ProductName + '-' + $ProductSemanticVersion - - $packageName = $productSemanticVersionWithName - if ($ProductNameSuffix) { - $packageName += "-$ProductNameSuffix" - } - - return [PSCustomObject]@{ - PackageName = $packageName - ProductVersionWithName = $productVersionWithName - ProductSemanticVersion = $ProductSemanticVersion - ProductSemanticVersionWithName = $productSemanticVersionWithName - ProductVersion = $ProductVersion - } -} - -function New-ExePackage { - param( - # Name of the Product - [ValidateNotNullOrEmpty()] - [string] $ProductName = 'PowerShell', - - # Version of the Product - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - - [string] $ProductVersion, - - # File describing the MSI Package creation semantics - [ValidateNotNullOrEmpty()] - [ValidateScript({Test-Path $_})] - [string] $BundleWxsPath = "$RepoRoot\assets\wix\bundle.wxs", - - # Architecture to use when creating the MSI - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture, - - # Location of the signed MSI - [Parameter(Mandatory = $true)] - [string] - $MsiLocationPath, - - [string] $CurrentLocation = (Get-Location) - ) - - $productNameSuffix = "win-$ProductTargetArchitecture" - - $windowsNames = Get-WindowsNames -ProductName $ProductName -ProductNameSuffix $productNameSuffix -ProductVersion $ProductVersion - $productSemanticVersionWithName = $windowsNames.ProductSemanticVersionWithName - $packageName = $windowsNames.PackageName - $isPreview = Test-IsPreview -Version $windowsNames.ProductSemanticVersion - - Write-Verbose "Create EXE for Product $productSemanticVersionWithName" -verbose - Write-Verbose "packageName = $packageName" -Verbose - - $exeLocationPath = Join-Path $CurrentLocation "$packageName.exe" - $exePdbLocationPath = Join-Path $CurrentLocation "$packageName.exe.wixpdb" - $windowsVersion = Get-WindowsVersion -packageName $packageName - - Start-MsiBuild -WxsFile $BundleWxsPath -ProductTargetArchitecture $ProductTargetArchitecture -Argument @{ - IsPreview = $isPreview - TargetPath = $MsiLocationPath - WindowsVersion = $windowsVersion - } -MsiLocationPath $exeLocationPath -MsiPdbLocationPath $exePdbLocationPath - - return $exeLocationPath -} - -<# -Allows you to extract the engine of exe package, mainly for signing -Any existing signature will be removed. - #> -function Expand-ExePackageEngine { - param( - # Location of the unsigned EXE - [Parameter(Mandatory = $true)] - [string] - $ExePath, - - # Location to put the expanded engine. - [Parameter(Mandatory = $true)] - [string] - $EnginePath, - - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture - ) - - <# - 2. detach the engine from TestInstaller.exe: - insignia -ib TestInstaller.exe -o engine.exe - #> - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $resolvedExePath = (Resolve-Path -Path $ExePath).ProviderPath - $resolvedEnginePath = [System.IO.Path]::GetFullPath($EnginePath) - - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixInsigniaExePath -ib $resolvedExePath -o $resolvedEnginePath} -} - -<# -Allows you to replace the engine (installer) in the exe package. -Used to replace the engine with a signed version -#> -function Compress-ExePackageEngine { - param( - # Location of the unsigned EXE - [Parameter(Mandatory = $true)] - [string] - $ExePath, - - # Location of the signed engine - [Parameter(Mandatory = $true)] - [string] - $EnginePath, - - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture - ) - - - <# - 4. re-attach the signed engine.exe to the bundle: - insignia -ab engine.exe TestInstaller.exe -o TestInstaller.exe - #> - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $resolvedEnginePath = (Resolve-Path -Path $EnginePath).ProviderPath - $resolvedExePath = (Resolve-Path -Path $ExePath).ProviderPath - - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixInsigniaExePath -ab $resolvedEnginePath $resolvedExePath -o $resolvedExePath} -} - -function New-MsiArgsArray { - param( - [Parameter(Mandatory)] - [Hashtable]$Argument - ) - - $buildArguments = @() - foreach ($key in $Argument.Keys) { - $buildArguments += "-d$key=$($Argument.$key)" - } - - return $buildArguments -} - -function Start-MsiBuild { - param( - [string[]] $WxsFile, - [string[]] $Extension = @('WixUIExtension', 'WixUtilExtension', 'WixBalExtension'), - [string] $ProductTargetArchitecture, - [Hashtable] $Argument, - [string] $MsiLocationPath, - [string] $MsiPdbLocationPath - ) - - $outDir = $env:Temp - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $extensionArgs = @() - foreach ($extensionName in $Extension) { - $extensionArgs += '-ext' - $extensionArgs += $extensionName - } - - $buildArguments = New-MsiArgsArray -Argument $Argument - - $objectPaths = @() - foreach ($file in $WxsFile) { - $fileName = [system.io.path]::GetFileNameWithoutExtension($file) - $objectPaths += Join-Path $outDir -ChildPath "${filename}.wixobj" - } - - foreach ($file in $objectPaths) { - Remove-Item -ErrorAction SilentlyContinue $file -Force - Remove-Item -ErrorAction SilentlyContinue $file -Force - } - - $resolvedWxsFiles = @() - foreach ($file in $WxsFile) { - $resolvedWxsFiles += (Resolve-Path -Path $file).ProviderPath - } - - Write-Verbose "$resolvedWxsFiles" -Verbose - - Write-Log "running candle..." - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixCandleExePath $resolvedWxsFiles -out "$outDir\\" $extensionArgs -arch $ProductTargetArchitecture $buildArguments -v} - - Write-Log "running light..." - # suppress ICE61, because we allow same version upgrades - # suppress ICE57, this suppresses an error caused by our shortcut not being installed per user - # suppress ICE40, REINSTALLMODE is defined in the Property table. - Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixLightExePath -sice:ICE61 -sice:ICE40 -sice:ICE57 -out $msiLocationPath -pdbout $msiPdbLocationPath $objectPaths $extensionArgs } - - foreach($file in $objectPaths) - { - Remove-Item -ErrorAction SilentlyContinue $file -Force - Remove-Item -ErrorAction SilentlyContinue $file -Force - } -} - <# .Synopsis Creates a Windows AppX MSIX package and assumes that the binaries are already built using 'Start-PSBuild'. @@ -4372,6 +3907,7 @@ function New-MSIXPackage Write-Verbose "Creating msix package" -Verbose Start-NativeExecution -VerboseOutputOnError { & $makeappx pack /o /v /h SHA256 /d $ProductSourcePath /p (Join-Path -Path $CurrentLocation -ChildPath "$packageName.msix") } Write-Verbose "Created $packageName.msix" -Verbose + Join-Path -Path $CurrentLocation -ChildPath "$packageName.msix" } } From 558c886d87c55979758576d19a9e6e105b93f70c Mon Sep 17 00:00:00 2001 From: Dongbo Wang <dongbow@microsoft.com> Date: Fri, 17 Apr 2026 10:05:33 -0700 Subject: [PATCH 253/255] Remove package verification from the notice pipeline (#27289) --- .pipelines/apiscan-gen-notice.yml | 4 ---- .pipelines/templates/compliance/generateNotice.yml | 7 ------- 2 files changed, 11 deletions(-) diff --git a/.pipelines/apiscan-gen-notice.yml b/.pipelines/apiscan-gen-notice.yml index 9cc83e7136a..df5ebaac091 100644 --- a/.pipelines/apiscan-gen-notice.yml +++ b/.pipelines/apiscan-gen-notice.yml @@ -8,9 +8,6 @@ parameters: displayName: Debugging - Enable CodeQL and set cadence to 1 hour type: boolean default: false - - name: SkipVerifyPackages - type: boolean - default: false variables: # PAT permissions NOTE: Declare a SymbolServerPAT variable in this group with a 'microsoft' organizanization scoped PAT with 'Symbols' Read permission. @@ -112,4 +109,3 @@ extends: - template: /.pipelines/templates/compliance/generateNotice.yml@self parameters: parentJobs: [] - SkipVerifyPackages: ${{ parameters.SkipVerifyPackages }} diff --git a/.pipelines/templates/compliance/generateNotice.yml b/.pipelines/templates/compliance/generateNotice.yml index 90fd08dd8d9..aec44b9b8f6 100644 --- a/.pipelines/templates/compliance/generateNotice.yml +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -4,8 +4,6 @@ parameters: - name: parentJobs type: jobList - - name: SkipVerifyPackages - type: boolean jobs: - job: generateNotice @@ -57,11 +55,6 @@ jobs: inputs: sourceScanPath: '$(repoRoot)\tools\cgmanifest\tpn' - - pwsh: | - $(repoRoot)/tools/clearlyDefined/ClearlyDefined.ps1 -TestAndHarvest - displayName: Verify that packages have license data - condition: eq(${{ parameters.SkipVerifyPackages }}, false) - - task: msospo.ospo-extension.8d7f9abb-6896-461d-9e25-4f74ed65ddb2.notice@0 displayName: 'NOTICE File Generator' inputs: From 6cc1e7071d40162e01fd2b47f6c33a02c0d046e6 Mon Sep 17 00:00:00 2001 From: PowerShell Team Bot <69177312+pwshBot@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:03:11 -0400 Subject: [PATCH 254/255] [master] Update branch for release (#27291) --- CHANGELOG/v7.7/dependencychanges.json | 15 +++ DotnetRuntimeMetadata.json | 2 +- global.json | 2 +- ...oft.PowerShell.Commands.Diagnostics.csproj | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../Microsoft.PowerShell.SDK.csproj | 10 +- .../Microsoft.WSMan.Management.csproj | 2 +- .../System.Management.Automation.csproj | 12 +- test/tools/TestService/TestService.csproj | 2 +- test/tools/WebListener/WebListener.csproj | 2 +- test/xUnit/xUnit.tests.csproj | 2 +- tools/cgmanifest/main/cgmanifest.json | 110 ++++++++---------- 14 files changed, 86 insertions(+), 81 deletions(-) create mode 100644 CHANGELOG/v7.7/dependencychanges.json diff --git a/CHANGELOG/v7.7/dependencychanges.json b/CHANGELOG/v7.7/dependencychanges.json new file mode 100644 index 00000000000..21cd1577251 --- /dev/null +++ b/CHANGELOG/v7.7/dependencychanges.json @@ -0,0 +1,15 @@ +[ + { + "ChangeType": "NonSecurity", + "Branch": "master", + "PackageId": ".NET SDK", + "FromVersion": "11.0.100-preview.2.26159.112", + "ToVersion": "11.0.100-preview.3.26207.106", + "VulnerabilityId": [], + "Severity": [], + "VulnerableRanges": [], + "AdvisoryUrls": [], + "Justification": "Updated .NET SDK. Building with the latest SDK is required.", + "TimestampUtc": "2026-04-17T17:16:15.7099916Z" + } +] diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index 7c4a2191467..f8288b53b67 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -4,7 +4,7 @@ "quality": "daily", "qualityFallback": "preview", "packageVersionPattern": "9.0.0-preview.6", - "sdkImageVersion": "11.0.100-preview.1.26104.118", + "sdkImageVersion": "11.0.100-preview.3.26207.106", "nextChannel": "9.0.0-preview.7", "azureFeed": "", "sdkImageOverride": "" diff --git a/global.json b/global.json index ef31c0723ff..d31f5220d83 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "11.0.100-preview.2.26159.112" + "version": "11.0.100-preview.3.26207.106" } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index 8e5a6b3f2e3..9552f72c83a 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ <ItemGroup> <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> - <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> <PropertyGroup> diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index 8bb897e0095..8ce2a97d67b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> </Project> diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index d6cd95677ab..cc98893c5b6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -33,7 +33,7 @@ <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" /> - <PackageReference Include="System.Drawing.Common" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Drawing.Common" Version="11.0.0-preview.3.26207.106" /> <PackageReference Include="JsonSchema.Net" Version="7.4.0" /> </ItemGroup> diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 1a63891ef77..dadff652e53 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.Diagnostics.EventLog" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.Diagnostics.EventLog" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> </Project> diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index ecc224713cc..50d431225c7 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,19 +16,19 @@ <ItemGroup> <!-- This section is to force the version of non-direct dependencies --> - <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="11.0.0-preview.3.26207.106" /> <!-- the following package(s) are from https://github.com/dotnet/fxdac --> <PackageReference Include="System.Data.SqlClient" Version="4.9.1" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.IO.Packaging" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="System.Net.Http.WinHttpHandler" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.IO.Packaging" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Net.Http.WinHttpHandler" Version="11.0.0-preview.3.26207.106" /> <!-- the following package(s) are from https://github.com/dotnet/wcf --> <PackageReference Include="System.ServiceModel.Http" Version="10.0.652802" /> <PackageReference Include="System.ServiceModel.NetTcp" Version="10.0.652802" /> <PackageReference Include="System.ServiceModel.Primitives" Version="10.0.652802" /> <!-- the source could not be found for the following package(s) --> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> <!-- diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index bc6e4b105de..d7b6d1235da 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> <ProjectReference Include="..\Microsoft.WSMan.Runtime\Microsoft.WSMan.Runtime.csproj" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> <PropertyGroup> diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index fa797a7f0de..beec0c4a495 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -32,12 +32,12 @@ <!-- the Application Insights package --> <PackageReference Include="Microsoft.ApplicationInsights" Version="2.23.0" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="System.Configuration.ConfigurationManager" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="System.DirectoryServices" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="System.Management" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="System.Security.Cryptography.Pkcs" Version="11.0.0-preview.2.26159.112" /> - <PackageReference Include="System.Security.Permissions" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Configuration.ConfigurationManager" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.DirectoryServices" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Management" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Security.Cryptography.Pkcs" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Security.Permissions" Version="11.0.0-preview.3.26207.106" /> <!-- the following package(s) are from the powershell org --> <PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" /> <PackageReference Include="Microsoft.PowerShell.Native" Version="700.0.0-rc.1" /> diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index 906820d262b..37b73426f6c 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.3.26207.106" /> <PackageReference Include="System.Data.SqlClient" Version="4.9.1" /> </ItemGroup> diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index c632e960a53..3567cd93c68 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="11.0.0-preview.2.26159.112" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> </Project> diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 4cf097c7956..a2a9fe041e9 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -30,7 +30,7 @@ <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="XunitXml.TestLogger" Version="8.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" /> </ItemGroup> <ItemGroup> diff --git a/tools/cgmanifest/main/cgmanifest.json b/tools/cgmanifest/main/cgmanifest.json index 5322bc102bd..5d2de074cae 100644 --- a/tools/cgmanifest/main/cgmanifest.json +++ b/tools/cgmanifest/main/cgmanifest.json @@ -1,5 +1,4 @@ { - "$schema": "https://json.schemastore.org/component-detection-manifest.json", "Registrations": [ { "Component": { @@ -86,7 +85,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -126,7 +125,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -166,7 +165,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -176,7 +175,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -186,7 +185,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -206,7 +205,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -216,7 +215,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -226,7 +225,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -236,7 +235,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -246,7 +245,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -256,7 +255,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -266,7 +265,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -276,7 +275,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -286,7 +285,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -296,7 +295,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -306,7 +305,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -316,7 +315,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -326,7 +325,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -336,7 +335,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -356,7 +355,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -366,7 +365,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -376,7 +375,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -436,7 +435,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -446,7 +445,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -456,7 +455,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -466,7 +465,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -476,7 +475,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -486,7 +485,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -506,7 +505,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -516,7 +515,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -526,7 +525,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -536,7 +535,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -546,7 +545,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -556,7 +555,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -566,7 +565,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -576,7 +575,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -586,7 +585,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -596,7 +595,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -606,7 +605,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -616,7 +615,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -626,7 +625,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -636,7 +635,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -646,7 +645,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -656,7 +655,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -706,7 +705,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -716,7 +715,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -726,17 +725,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "10.0.5" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Text.Encoding.CodePages", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -756,10 +745,11 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "10.0.5" + "Version": "10.0.6" } }, "DevelopmentDependency": false } - ] + ], + "$schema": "https://json.schemastore.org/component-detection-manifest.json" } From 2475feb87698ca064e561d85abea5ed643152364 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke <SeeminglyScience@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:49:52 -0400 Subject: [PATCH 255/255] Enable usage in AppContainers (#27266) Co-authored-by: Travis Plunk <travis.plunk@microsoft.com> --- .../engine/Modules/ModuleIntrinsics.cs | 4 ++- .../namespaces/FileSystemProvider.cs | 27 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 301ee8c3b49..538c4775f0a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -967,7 +967,9 @@ internal static string GetPersonalModulePath() #if UNIX return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES); #else - string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank ? string.Empty : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank + ? string.Empty + : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, Environment.SpecialFolderOption.DoNotVerify); return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory); #endif } diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 71de4e8ac67..05c8fff76e0 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -569,7 +569,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) if (driveIsFixed) { // Since the drive is fixed, ensure the root is valid. - validDrive = Directory.Exists(drive.Root); + validDrive = SafeDoesPathExist(drive.Root); } if (validDrive) @@ -908,7 +908,7 @@ protected override Collection<PSDriveInfo> InitializeDefaultDrives() if (newDrive.DriveType == DriveType.Fixed) { - if (!newDrive.RootDirectory.Exists) + if (!SafeDoesPathExist(newDrive.RootDirectory.FullName)) { continue; } @@ -1227,6 +1227,29 @@ protected override void GetItem(string path) } } + private static bool SafeDoesPathExist(string rootDirectory) + { + if (Directory.Exists(rootDirectory)) + { + return true; + } + + try + { + return (File.GetAttributes(rootDirectory) & FileAttributes.Directory) is not 0; + } + // In some scenarios (like AppContainers) direct access to the root directory may + // be prevented, but more specific paths may be accessible. + catch (UnauthorizedAccessException) + { + return true; + } + catch + { + return false; + } + } + private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool showHidden) { path = NormalizePath(path);