Skip to content

Conversation

@TravisEz13
Copy link
Member

Backport of #26157 to release/v7.6

Triggered by @TravisEz13 on behalf of @adityapatwardhan

Original CL Label: CL-General

/cc @PowerShell/powershell-maintainers

Impact

REQUIRED: Choose either Tooling Impact or Customer Impact (or both). At least one checkbox must be selected.

Tooling Impact

  • Required tooling change
  • Optional tooling change (include reasoning)

Updates the Linux and Windows GitHub workflows to download the latest DSC package so CI and packaging continue to use supported binaries.

Customer Impact

  • Customer reported
  • Found internally

Enables administrators to manage all PowerShell profile types via DSC v3 so profile content stays consistent across machines; without it, profiles must be maintained manually.

Regression

REQUIRED: Check exactly one box.

  • Yes
  • No

This is not a regression.

Testing

Validated the PowerShell Profile DSC resource using the new Pester suite added in the original change, confirmed the DSC package download logic via CI runs on master, and manually exercised DSC export/get scenarios to ensure consistent behavior.

Risk

REQUIRED: Check exactly one box.

  • High
  • Medium
  • Low

Adds a new DSC resource and packaging entries but is isolated to the DSC subsystem and experimental feature registration, minimizing blast radius.

Copilot AI review requested due to automatic review settings November 13, 2025 22:21
@TravisEz13 TravisEz13 added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Nov 13, 2025
@TravisEz13 TravisEz13 requested a review from a team as a code owner November 13, 2025 22:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request backports DSC v3 resource support for managing PowerShell profiles from the master branch to the release/v7.6 branch. It enables administrators to use DSC v3 to consistently manage PowerShell profile content across all profile types (CurrentUserCurrentHost, CurrentUserAllHosts, AllUsersCurrentHost, AllUsersAllHosts) on Windows, Linux, and macOS.

Key Changes:

  • Adds DSC v3 resource implementation for PowerShell profile management
  • Updates CI workflows to download and use DSC v3.2+ packages for testing
  • Registers PSProfileDSCResource as an experimental feature

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
dsc/pwsh.profile.resource.ps1 Implements the DSC resource logic for get/set/export operations on PowerShell profiles
dsc/pwsh.profile.dsc.resource.json Provides the DSC v3 resource manifest with schema definitions and operation specifications
src/powershell-win-core/powershell-win-core.csproj Includes DSC resource files in Windows builds using wildcard pattern
src/powershell-unix/powershell-unix.csproj Includes DSC resource files in Unix/Linux/macOS builds with explicit file listing
src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs Registers PSProfileDSCResource as an experimental feature
experimental-feature-windows.json Enables the PSProfileDSCResource experimental feature on Windows
experimental-feature-linux.json Enables the PSProfileDSCResource experimental feature on Linux
tools/packaging/boms/windows.json Adds DSC resource files to Windows packaging BOM
tools/packaging/boms/linux.json Adds DSC resource files to Linux packaging BOM
tools/packaging/boms/mac.json Adds DSC resource files to macOS packaging BOM
test/powershell/dsc/dsc.profileresource.Tests.ps1 Comprehensive Pester test suite for the DSC profile resource covering all profile types
test/powershell/dsc/psprofile_*.dsc.yaml Test configuration files demonstrating various profile management scenarios
.github/actions/test/windows/action.yml Downloads DSC v3.2+ package for Windows CI testing
.github/actions/test/nix/action.yml Downloads platform-specific DSC v3.2+ packages for Linux and macOS CI testing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no releases match the filter criteria (Major = 3, Minor >= 2), $latestRelease will be $null, causing subsequent property accesses to fail. Add validation to handle this case gracefully.

Add error handling:

$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
if (-not $latestRelease) {
    throw "No DSC v3.2+ release found"
}
$latestVersion = $latestRelease.tag_name.TrimStart("v")

This prevents cryptic errors when the expected DSC version isn't available.

Suggested change
$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
$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
if (-not $latestRelease) {
throw "No DSC v3.2+ release found"
}

Copilot uses AI. Check for mistakes.
# Default constructor logic - read from file system
$fileExists = Test-Path $resource.profilePath
if ($fileExists) {
$resource.content = Get-Content -Path $resource.profilePath
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Get-Content call returns an array of strings (one per line) by default, but this should return the entire content as a single string to match the expected behavior based on the test expectations and the Set-Content usage in line 147.

Add the -Raw parameter to return the content as a single string:

$resource.content = Get-Content -Path $resource.profilePath -Raw

This ensures consistency with how profile content is expected to be handled throughout the resource (as a single string, not an array).

Suggested change
$resource.content = Get-Content -Path $resource.profilePath
$resource.content = Get-Content -Path $resource.profilePath -Raw

Copilot uses AI. Check for mistakes.
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="..\..\dsc\pwsh.profile.dsc.resource.json;..\..\dsc\pwsh.profile.resource.ps1">
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Unix project file explicitly lists the two DSC files individually, while the Windows project file uses a wildcard pattern ..\..\dsc\* (line 32). This creates an inconsistency where future DSC files added to the /dsc folder would be automatically included in Windows builds but would need manual addition to the Unix project file.

For consistency and maintainability, use the same wildcard pattern as Windows:

<Content Include="..\..\dsc\*">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>

This ensures both platforms handle DSC resource files consistently and reduces maintenance burden when adding new DSC resources.

Suggested change
<Content Include="..\..\dsc\pwsh.profile.dsc.resource.json;..\..\dsc\pwsh.profile.resource.ps1">
<Content Include="..\..\dsc\*">

Copilot uses AI. Check for mistakes.
New-Item -Path $testProfilePathAllUsersAllHosts -Value $testProfileContent -Force -ItemType File

$originalPath = $env:PATH
$env:PATH += "$pathSeparator$PSHome"
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 156 duplicates the PATH modification already performed in line 131. This creates redundant PATH entries.

Remove the duplicate line 156:

$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
# Line 156 removed - $PSHome already added to PATH in line 131
Suggested change
$env:PATH += "$pathSeparator$PSHome"

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no releases match the filter criteria (Major = 3, Minor >= 2), $latestRelease will be $null, causing subsequent property accesses to fail. Add validation to handle this case gracefully.

Add error handling:

$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
if (-not $latestRelease) {
    throw "No DSC v3.2+ release found"
}
$latestVersion = $latestRelease.tag_name.TrimStart("v")

This prevents cryptic errors when the expected DSC version isn't available.

Suggested change
$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
$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
if (-not $latestRelease) {
throw "No DSC v3.2+ release found"
}

Copilot uses AI. Check for mistakes.
Remove-Item $testProfilePathAllUsersAllHosts -Force -ErrorAction SilentlyContinue
}

Remove-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line attempts to remove a file that doesn't exist. Based on the BeforeAll block (line 147), the backup file is named allusers-currenthost-profile.bak, not currentuser-allhosts-profile.bak.

Update to match the correct backup filename:

Remove-Item -Path "$TestDrive/allusers-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue
Remove-Item -Path "$TestDrive/allusers-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue
Suggested change
Remove-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue
Remove-Item -Path "$TestDrive/allusers-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue

Copilot uses AI. Check for mistakes.
$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
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no asset matches the filter pattern, $downloadUrl will be $null, causing Invoke-RestMethod to fail with an unclear error. Add validation to handle this case.

Add error handling:

$downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url
if (-not $downloadUrl) {
    throw "No matching DSC package found for Windows x86_64"
}
Write-Host "Download URL: $downloadUrl"

This provides a clear error message if the expected package isn't found in the release.

Suggested change
$downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url
$downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url
if (-not $downloadUrl) {
throw "No matching DSC package found for Windows x86_64"
}

Copilot uses AI. Check for mistakes.
@TravisEz13 TravisEz13 merged commit 1684824 into PowerShell:release/v7.6 Nov 13, 2025
38 checks passed
@TravisEz13 TravisEz13 deleted the backport/release/v7.6/26157-5715d3339 branch November 13, 2025 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants