diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml
index b338c39834..89c11d59cc 100644
--- a/.github/actions/test/nix/action.yml
+++ b/.github/actions/test/nix/action.yml
@@ -45,6 +45,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 734e30208f..1b2f6c56a6 100644
--- a/.github/actions/test/windows/action.yml
+++ b/.github/actions/test/windows/action.yml
@@ -45,6 +45,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 0000000000..cd18e94eec
--- /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 0000000000..ad9cfa4a63
--- /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 ca5b49878a..31f7b965a5 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 ca5b49878a..31f7b965a5 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 dd26e60964..60d6b23806 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -25,6 +25,7 @@ namespace System.Management.Automation
internal const string PSNativeWindowsTildeExpansion = nameof(PSNativeWindowsTildeExpansion);
internal const string PSRedirectToVariable = "PSRedirectToVariable";
internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber);
+ internal const string PSProfileDSCResource = "PSProfileDSCResource";
#endregion
@@ -125,6 +126,10 @@ namespace System.Management.Automation
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 802acf05e3..20c61247d2 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 5368518dd3..e6efeac10f 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 0000000000..f361e67ea8
--- /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 0000000000..356119826c
--- /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 0000000000..bc51f0a439
--- /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 0000000000..8ed8d98c3a
--- /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 0000000000..5a42c28eb9
--- /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 0000000000..76439387d2
--- /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 0000000000..cf7881b5df
--- /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 9f5f886a4c..db29e47a29 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 a4b5bc2bff..af4fcb0c96 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 d8857c7178..af4d1736ed 100644
--- a/tools/packaging/boms/windows.json
+++ b/tools/packaging/boms/windows.json
@@ -4425,5 +4425,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
}
]