mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 03:07:56 +01:00
Initial DSC v3 support for PowerToys (#41132)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Tasks checklist - [X] Implement DSC infra in PowerToys - [X] Implement Settings DSC resource - [X] Implement Get, Set, Test, Export, Schema - [X] Generate manifest (DSC resource JSON) - [X] Added Unit Tests - [x] Add `NJsonSchema` v11.4.0 to the stream - [x] Package the manifest files so dsc.exe can discover them - [x] Add `PowerToys.DSC.exe` to the PATH (maybe?) - [x] Add `InstallLocation` in the registry key so `winget configue export` can export the PowerToys DSC resources <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [X] Closes: #37276 - [X] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [X] **Tests:** Added/updated and all pass - [X] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [x] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [x] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [x] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [x] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: vanzue <vanzue@outlook.com> Co-authored-by: Kai Tao (from Dev Box) <kaitao@microsoft.com> Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
This commit is contained in:
4
.github/actions/spell-check/expect.txt
vendored
4
.github/actions/spell-check/expect.txt
vendored
@@ -160,6 +160,7 @@ BUILDARCH
|
||||
BUILDNUMBER
|
||||
buildtransitive
|
||||
builttoroam
|
||||
BUNDLEINFO
|
||||
BVal
|
||||
BValue
|
||||
byapp
|
||||
@@ -863,6 +864,7 @@ LOCKTYPE
|
||||
LOGFONT
|
||||
LOGFONTW
|
||||
logon
|
||||
LOGMSG
|
||||
LOGPIXELSX
|
||||
LOGPIXELSY
|
||||
LOn
|
||||
@@ -1040,6 +1042,7 @@ MWBEx
|
||||
MYICON
|
||||
NAMECHANGE
|
||||
namespaceanddescendants
|
||||
Namotion
|
||||
nao
|
||||
NCACTIVATE
|
||||
ncc
|
||||
@@ -1077,6 +1080,7 @@ NEWPLUSSHELLEXTENSIONWIN
|
||||
newrow
|
||||
nicksnettravels
|
||||
NIF
|
||||
NJson
|
||||
NLog
|
||||
NLSTEXT
|
||||
NMAKE
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -350,7 +350,9 @@ src/common/Telemetry/*.etl
|
||||
|
||||
# Generated installer file for Monaco source files.
|
||||
/installer/PowerToysSetup/MonacoSRC.wxs
|
||||
/installer/PowerToysSetup/DscResources.wxs
|
||||
/installer/PowerToysSetupVNext/MonacoSRC.wxs
|
||||
/installer/PowerToysSetupVNext/DscResources.wxs
|
||||
|
||||
# MSBuildCache
|
||||
/MSBuildCacheLogs/
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"PowerToys.Awake.exe",
|
||||
"PowerToys.Awake.dll",
|
||||
|
||||
|
||||
"PowerToys.FancyZonesEditor.exe",
|
||||
"PowerToys.FancyZonesEditor.dll",
|
||||
"PowerToys.FancyZonesEditorCommon.dll",
|
||||
@@ -230,7 +229,10 @@
|
||||
|
||||
"PowerToys.CmdPalModuleInterface.dll",
|
||||
"CmdPalKeyboardService.dll",
|
||||
"*Microsoft.CmdPal.UI_*.msix"
|
||||
"*Microsoft.CmdPal.UI_*.msix",
|
||||
|
||||
"PowerToys.DSC.dll",
|
||||
"PowerToys.DSC.exe"
|
||||
],
|
||||
"SigningInfo": {
|
||||
"Operations": [
|
||||
@@ -297,6 +299,9 @@
|
||||
"msvcp140_1_app.dll",
|
||||
"msvcp140_2_app.dll",
|
||||
"msvcp140_app.dll",
|
||||
"Namotion.Reflection.dll",
|
||||
"NJsonSchema.Annotations.dll",
|
||||
"NJsonSchema.dll",
|
||||
"vcamp140_app.dll",
|
||||
"vccorlib140_app.dll",
|
||||
"vcomp140_app.dll",
|
||||
|
||||
88
.pipelines/generateDscManifests.ps1
Normal file
88
.pipelines/generateDscManifests.ps1
Normal file
@@ -0,0 +1,88 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BuildPlatform,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BuildConfiguration,
|
||||
|
||||
[Parameter()]
|
||||
[string]$RepoRoot = (Get-Location).Path
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Resolve-PlatformDirectory {
|
||||
param(
|
||||
[string]$Root,
|
||||
[string]$Platform
|
||||
)
|
||||
|
||||
$normalized = $Platform.Trim()
|
||||
$candidates = @()
|
||||
$candidates += Join-Path $Root $normalized
|
||||
$candidates += Join-Path $Root ($normalized.ToUpperInvariant())
|
||||
$candidates += Join-Path $Root ($normalized.ToLowerInvariant())
|
||||
$candidates = $candidates | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if (Test-Path $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
|
||||
return $candidates[0]
|
||||
}
|
||||
|
||||
Write-Host "Repo root: $RepoRoot"
|
||||
Write-Host "Requested build platform: $BuildPlatform"
|
||||
Write-Host "Requested configuration: $BuildConfiguration"
|
||||
|
||||
# Always use x64 PowerToys.DSC.exe since CI/CD machines are x64
|
||||
$exePlatform = 'x64'
|
||||
$exeRoot = Resolve-PlatformDirectory -Root $RepoRoot -Platform $exePlatform
|
||||
$exeOutputDir = Join-Path $exeRoot $BuildConfiguration
|
||||
$exePath = Join-Path $exeOutputDir 'PowerToys.DSC.exe'
|
||||
|
||||
Write-Host "Using x64 PowerToys.DSC.exe to generate DSC manifests for $BuildPlatform build"
|
||||
|
||||
if (-not (Test-Path $exePath)) {
|
||||
throw "PowerToys.DSC.exe not found at '$exePath'. Make sure it has been built first."
|
||||
}
|
||||
|
||||
Write-Host "Using PowerToys.DSC.exe at '$exePath'."
|
||||
|
||||
# Output DSC manifests to the target build platform directory (x64, ARM64, etc.)
|
||||
$outputRoot = Resolve-PlatformDirectory -Root $RepoRoot -Platform $BuildPlatform
|
||||
if (-not (Test-Path $outputRoot)) {
|
||||
Write-Host "Creating missing platform output root at '$outputRoot'."
|
||||
New-Item -Path $outputRoot -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
$outputDir = Join-Path $outputRoot $BuildConfiguration
|
||||
if (-not (Test-Path $outputDir)) {
|
||||
Write-Host "Creating missing configuration output directory at '$outputDir'."
|
||||
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "DSC manifests will be generated to: '$outputDir'"
|
||||
|
||||
Write-Host "Cleaning previously generated DSC manifest files from '$outputDir'."
|
||||
Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
|
||||
|
||||
$arguments = @('manifest', '--resource', 'settings', '--outputDir', $outputDir)
|
||||
Write-Host "Invoking DSC manifest generator: '$exePath' $($arguments -join ' ')"
|
||||
& $exePath @arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "PowerToys.DSC.exe exited with code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$generatedFiles = Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
|
||||
if ($generatedFiles.Count -eq 0) {
|
||||
throw "No DSC manifest files were generated in '$outputDir'."
|
||||
}
|
||||
|
||||
Write-Host "Generated $($generatedFiles.Count) DSC manifest file(s):"
|
||||
foreach ($file in $generatedFiles) {
|
||||
Write-Host " - $($file.FullName)"
|
||||
}
|
||||
@@ -271,6 +271,23 @@ jobs:
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
|
||||
# Build PowerToys.DSC.exe for ARM64 (x64 uses existing binary from previous build)
|
||||
- task: VSBuild@1
|
||||
displayName: Build PowerToys.DSC.exe (x64 for generating manifests)
|
||||
condition: ne(variables['BuildPlatform'], 'x64')
|
||||
inputs:
|
||||
solution: src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj
|
||||
msbuildArgs: /t:Build /m /restore
|
||||
platform: x64
|
||||
configuration: $(BuildConfiguration)
|
||||
msbuildArchitecture: x64
|
||||
maximumCpuCount: true
|
||||
|
||||
# Generate DSC manifests using PowerToys.DSC.exe
|
||||
- pwsh: |-
|
||||
& '.pipelines/generateDscManifests.ps1' -BuildPlatform '$(BuildPlatform)' -BuildConfiguration '$(BuildConfiguration)' -RepoRoot '$(Build.SourcesDirectory)'
|
||||
displayName: Generate DSC manifests
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Stage SDK/build
|
||||
inputs:
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="MSTest" Version="3.8.3" />
|
||||
<PackageVersion Include="NJsonSchema" Version="11.4.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="NLog" Version="5.2.8" />
|
||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
|
||||
|
||||
@@ -1521,6 +1521,7 @@ SOFTWARE.
|
||||
- ModernWpfUI
|
||||
- Moq
|
||||
- MSTest
|
||||
- NJsonSchema
|
||||
- NLog
|
||||
- NLog.Extensions.Logging
|
||||
- NLog.Schema
|
||||
|
||||
@@ -797,6 +797,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v3", "v3", "{9605B84E-FAC4-477B-B9EC-0753177EE6A8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.DSC", "src\dsc\v3\PowerToys.DSC\PowerToys.DSC.csproj", "{94CDC147-6137-45E9-AEDE-17FF809607C0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.DSC.UnitTests", "src\dsc\v3\PowerToys.DSC.UnitTests\PowerToys.DSC.UnitTests.csproj", "{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
|
||||
@@ -2891,6 +2897,22 @@ Global
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|x64.Build.0 = Debug|x64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|x64.ActiveCfg = Release|x64
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|x64.Build.0 = Release|x64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|x64.Build.0 = Debug|x64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|x64.ActiveCfg = Release|x64
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -3238,6 +3260,9 @@ Global
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
|
||||
{9605B84E-FAC4-477B-B9EC-0753177EE6A8} = {557C4636-D7E1-4838-A504-7D19B725EE95}
|
||||
{94CDC147-6137-45E9-AEDE-17FF809607C0} = {9605B84E-FAC4-477B-B9EC-0753177EE6A8}
|
||||
{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78} = {9605B84E-FAC4-477B-B9EC-0753177EE6A8}
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
|
||||
83
doc/dsc/Settings.md
Normal file
83
doc/dsc/Settings.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Settings resource
|
||||
Manage the settings for PowerToys modules
|
||||
|
||||
## Commands
|
||||
|
||||
### ✨ Modules
|
||||
List all the modules supported by the settings resource.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe modules --resource 'settings'
|
||||
AdvancedPaste
|
||||
AlwaysOnTop
|
||||
App
|
||||
Awake
|
||||
ColorPicker
|
||||
CropAndLock
|
||||
EnvironmentVariables
|
||||
FancyZones
|
||||
FileLocksmith
|
||||
FindMyMouse
|
||||
Hosts
|
||||
ImageResizer
|
||||
KeyboardManager
|
||||
MeasureTool
|
||||
MouseHighlighter
|
||||
MouseJump
|
||||
MousePointerCrosshairs
|
||||
Peek
|
||||
PowerAccent
|
||||
PowerOCR
|
||||
PowerRename
|
||||
RegistryPreview
|
||||
ShortcutGuide
|
||||
Workspaces
|
||||
ZoomIt
|
||||
```
|
||||
|
||||
### 📄 Get
|
||||
Get the settings for a specific module.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe get --resource 'settings' --module EnvironmentVariables
|
||||
{"settings":{"properties":{"LaunchAdministrator":{"value":true}},"name":"EnvironmentVariables","version":"1.0"}}
|
||||
```
|
||||
|
||||
### 🖨️ Export
|
||||
Export the settings for a specific module.
|
||||
|
||||
ℹ️ Settings resource Get and Export operation output states are identical.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe get --resource 'settings' --module EnvironmentVariables
|
||||
{"settings":{"properties":{"LaunchAdministrator":{"value":true}},"name":"EnvironmentVariables","version":"1.0"}}
|
||||
```
|
||||
|
||||
### 📝 Set
|
||||
Set the settings for a specific module. This command will update the settings to the specified values.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe set --resource 'settings' --module Awake --input '{"settings":{"properties":{"keepDisplayOn":false,"mode":0,"intervalHours":0,"intervalMinutes":1,"expirationDateTime":"2025-08-13T10:10:00.000001-07:00","customTrayTimes":{}},"name":"Awake","version":"0.0.1"}}'
|
||||
{"settings":{"properties":{"keepDisplayOn":false,"mode":0,"intervalHours":0,"intervalMinutes":1,"expirationDateTime":"2025-08-13T10:10:00.000001-07:00","customTrayTimes":{}},"name":"Awake","version":"0.0.1"}}
|
||||
["settings"]
|
||||
```
|
||||
|
||||
### 🧪 Test
|
||||
Test the settings for a specific module. This command will check if the current settings match the desired state.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe test --resource 'settings' --module Awake --input '{"settings":{"properties":{"keepDisplayOn":false,"mode":0,"intervalHours":0,"intervalMinutes":1,"expirationDateTime":"2025-08-13T10:10:00.000002-07:00","customTrayTimes":{}},"name":"Awake","version":"0.0.1"}}'
|
||||
{"settings":{"properties":{"keepDisplayOn":false,"mode":0,"intervalHours":0,"intervalMinutes":1,"expirationDateTime":"2025-08-13T10:10:00.000001-07:00","customTrayTimes":{}},"name":"Awake","version":"0.0.1"},"_inDesiredState":false}
|
||||
["settings"]
|
||||
```
|
||||
|
||||
### 🛠️ Schema
|
||||
Generates the JSON schema for the settings resource of a specific module.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe schema --resource 'settings' --module Awake
|
||||
{"$schema":"http://json-schema.org/draft-04/schema#","title":"SettingsResourceObjectOfAwakeSettings","type":"object","additionalProperties":false,"required":["settings"],"properties":{"_inDesiredState":{"type":["boolean","null"],"description":"Indicates whether an instance is in the desired state"},"settings":{"description":"The settings content for the module."}}}
|
||||
PS E:\src\powertoys> PowerToys.DSC.exe schema --resource 'settings' --module Awake | Format-Json
|
||||
```
|
||||
|
||||
### 📦 Manifest
|
||||
Generates a manifest dsc resource JSON file for the specified module.
|
||||
- If the module is not specified, it will generate a manifest for all modules.
|
||||
- If the output directory is not specified, it will print the manifest to the console.
|
||||
```shell
|
||||
PS C:\> PowerToys.DSC.exe manifest --resource settings --module 'Awake' --outputDir "C:\manifests"
|
||||
```
|
||||
@@ -16,6 +16,7 @@ SET PTRoot=$(SolutionDir)\..
|
||||
call "..\..\..\publish.cmd" x64
|
||||
)
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs"
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' != 'x64'">
|
||||
@@ -26,6 +27,7 @@ SET PTRoot=$(SolutionDir)\..
|
||||
call "..\..\..\publish.cmd" arm64
|
||||
)
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs"
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@@ -121,6 +123,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
<Compile Include="Hosts.wxs" />
|
||||
<Compile Include="ImageResizer.wxs" />
|
||||
<Compile Include="KeyboardManager.wxs" />
|
||||
<Compile Include="DscResources.wxs" />
|
||||
<Compile Include="Peek.wxs" />
|
||||
<Compile Include="PowerRename.wxs" />
|
||||
<Compile Include="RegistryPreview.wxs" />
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<ComponentGroupRef Id="NewPlusComponentGroup" />
|
||||
<ComponentGroupRef Id="NewPlusTemplatesComponentGroup" />
|
||||
<ComponentGroupRef Id="ResourcesComponentGroup" />
|
||||
<ComponentGroupRef Id="DscResourcesComponentGroup" />
|
||||
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
|
||||
<ComponentGroupRef Id="ToolComponentGroup" />
|
||||
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
||||
@@ -324,7 +325,6 @@
|
||||
BinaryKey="PTCustomActions"
|
||||
DllEntry="UninstallDSCModuleCA"
|
||||
/>
|
||||
|
||||
<CustomAction Id="UninstallServicesTask"
|
||||
Return="ignore"
|
||||
Impersonate="yes"
|
||||
|
||||
87
installer/PowerToysSetup/generateDscResourcesWxs.ps1
Normal file
87
installer/PowerToysSetup/generateDscResourcesWxs.ps1
Normal file
@@ -0,0 +1,87 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string]$dscWxsFile,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string]$Platform,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string]$Configuration
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$buildOutputDir = Join-Path $scriptDir "..\..\$Platform\$Configuration"
|
||||
|
||||
if (-not (Test-Path $buildOutputDir)) {
|
||||
Write-Error "Build output directory not found: '$buildOutputDir'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$dscFiles = Get-ChildItem -Path $buildOutputDir -Filter "microsoft.powertoys.*.settings.dsc.resource.json" -File
|
||||
|
||||
if (-not $dscFiles) {
|
||||
Write-Warning "No DSC manifest files found in '$buildOutputDir'"
|
||||
$wxsContent = @"
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?include `$(sys.CURRENTDIR)\Common.wxi?>
|
||||
<Fragment>
|
||||
<ComponentGroup Id="DscResourcesComponentGroup">
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
"@
|
||||
Set-Content -Path $dscWxsFile -Value $wxsContent
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "Found $($dscFiles.Count) DSC manifest file(s)"
|
||||
|
||||
$wxsContent = @"
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?include `$(sys.CURRENTDIR)\Common.wxi?>
|
||||
<Fragment>
|
||||
<DirectoryRef Id="DSCModulesReferenceFolder">
|
||||
"@
|
||||
|
||||
$componentRefs = @()
|
||||
foreach ($file in $dscFiles) {
|
||||
$componentId = "DscResource_" + ($file.BaseName -replace '[^A-Za-z0-9_]', '_')
|
||||
$fileId = $componentId + "_File"
|
||||
$guid = [System.Guid]::NewGuid().ToString().ToUpper()
|
||||
$componentRefs += $componentId
|
||||
|
||||
$wxsContent += @"
|
||||
|
||||
<Component Id="$componentId" Guid="{$guid}">
|
||||
<RegistryKey Root="`$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="$componentId" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Id="$fileId" Source="`$(var.BinDir)$($file.Name)" Vital="no"/>
|
||||
</Component>
|
||||
"@
|
||||
}
|
||||
|
||||
$wxsContent += @"
|
||||
|
||||
</DirectoryRef>
|
||||
</Fragment>
|
||||
<Fragment>
|
||||
<ComponentGroup Id="DscResourcesComponentGroup">
|
||||
"@
|
||||
|
||||
foreach ($componentId in $componentRefs) {
|
||||
$wxsContent += @"
|
||||
|
||||
<ComponentRef Id="$componentId"/>
|
||||
"@
|
||||
}
|
||||
|
||||
$wxsContent += @"
|
||||
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
"@
|
||||
|
||||
Set-Content -Path $dscWxsFile -Value $wxsContent
|
||||
Write-Host "Generated DSC resources WiX fragment: '$dscWxsFile'"
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "RcResource.h"
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <spdlog/sinks/base_sink.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../../src/common/logger/logger.h"
|
||||
#include "../../src/common/utils/gpo.h"
|
||||
@@ -232,7 +233,9 @@ UINT __stdcall LaunchPowerToysCA(MSIHANDLE hInstall)
|
||||
|
||||
auto action = [&commandLine](HANDLE userToken)
|
||||
{
|
||||
STARTUPINFO startupInfo{.cb = sizeof(STARTUPINFO), .wShowWindow = SW_SHOWNORMAL};
|
||||
STARTUPINFO startupInfo = { 0 };
|
||||
startupInfo.cb = sizeof(STARTUPINFO);
|
||||
startupInfo.wShowWindow = SW_SHOWNORMAL;
|
||||
PROCESS_INFORMATION processInformation;
|
||||
|
||||
PVOID lpEnvironment = NULL;
|
||||
@@ -271,7 +274,9 @@ UINT __stdcall LaunchPowerToysCA(MSIHANDLE hInstall)
|
||||
}
|
||||
else
|
||||
{
|
||||
STARTUPINFO startupInfo{.cb = sizeof(STARTUPINFO), .wShowWindow = SW_SHOWNORMAL};
|
||||
STARTUPINFO startupInfo = { 0 };
|
||||
startupInfo.cb = sizeof(STARTUPINFO);
|
||||
startupInfo.wShowWindow = SW_SHOWNORMAL;
|
||||
|
||||
PROCESS_INFORMATION processInformation;
|
||||
|
||||
@@ -424,7 +429,7 @@ UINT __stdcall InstallDSCModuleCA(MSIHANDLE hInstall)
|
||||
const auto modulesPath = baseModulesPath / L"Microsoft.PowerToys.Configure" / (get_product_version(false) + L".0");
|
||||
|
||||
std::error_code errorCode;
|
||||
fs::create_directories(modulesPath, errorCode);
|
||||
std::filesystem::create_directories(modulesPath, errorCode);
|
||||
if (errorCode)
|
||||
{
|
||||
hr = E_FAIL;
|
||||
@@ -433,7 +438,7 @@ UINT __stdcall InstallDSCModuleCA(MSIHANDLE hInstall)
|
||||
|
||||
for (const auto *filename : {DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME})
|
||||
{
|
||||
fs::copy_file(fs::path(installationFolder) / "DSCModules" / filename, modulesPath / filename, fs::copy_options::overwrite_existing, errorCode);
|
||||
std::filesystem::copy_file(std::filesystem::path(installationFolder) / "DSCModules" / filename, modulesPath / filename, std::filesystem::copy_options::overwrite_existing, errorCode);
|
||||
|
||||
if (errorCode)
|
||||
{
|
||||
@@ -481,7 +486,7 @@ UINT __stdcall UninstallDSCModuleCA(MSIHANDLE hInstall)
|
||||
|
||||
for (const auto *filename : {DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME})
|
||||
{
|
||||
fs::remove(versionedModulePath / filename, errorCode);
|
||||
std::filesystem::remove(versionedModulePath / filename, errorCode);
|
||||
|
||||
if (errorCode)
|
||||
{
|
||||
@@ -492,7 +497,7 @@ UINT __stdcall UninstallDSCModuleCA(MSIHANDLE hInstall)
|
||||
|
||||
for (const auto *modulePath : {&versionedModulePath, &powerToysModulePath})
|
||||
{
|
||||
fs::remove(*modulePath, errorCode);
|
||||
std::filesystem::remove(*modulePath, errorCode);
|
||||
|
||||
if (errorCode)
|
||||
{
|
||||
@@ -1375,6 +1380,120 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
UINT __stdcall SetBundleInstallLocationCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
|
||||
// Declare all variables at the beginning to avoid goto issues
|
||||
std::wstring customActionData;
|
||||
std::wstring installationFolder;
|
||||
std::wstring bundleUpgradeCode;
|
||||
std::wstring installScope;
|
||||
bool isPerUser = false;
|
||||
size_t pos1 = std::wstring::npos;
|
||||
size_t pos2 = std::wstring::npos;
|
||||
std::vector<HKEY> keysToTry;
|
||||
|
||||
hr = WcaInitialize(hInstall, "SetBundleInstallLocationCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
// Parse CustomActionData: "installFolder;upgradeCode;installScope"
|
||||
hr = getInstallFolder(hInstall, customActionData);
|
||||
ExitOnFailure(hr, "Failed to get CustomActionData.");
|
||||
|
||||
pos1 = customActionData.find(L';');
|
||||
if (pos1 == std::wstring::npos)
|
||||
{
|
||||
hr = E_INVALIDARG;
|
||||
ExitOnFailure(hr, "Invalid CustomActionData format - missing first semicolon");
|
||||
}
|
||||
|
||||
pos2 = customActionData.find(L';', pos1 + 1);
|
||||
if (pos2 == std::wstring::npos)
|
||||
{
|
||||
hr = E_INVALIDARG;
|
||||
ExitOnFailure(hr, "Invalid CustomActionData format - missing second semicolon");
|
||||
}
|
||||
|
||||
installationFolder = customActionData.substr(0, pos1);
|
||||
bundleUpgradeCode = customActionData.substr(pos1 + 1, pos2 - pos1 - 1);
|
||||
installScope = customActionData.substr(pos2 + 1);
|
||||
|
||||
isPerUser = (installScope == L"perUser");
|
||||
|
||||
// Use the appropriate registry based on install scope
|
||||
HKEY targetKey = isPerUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
|
||||
const wchar_t* keyName = isPerUser ? L"HKCU" : L"HKLM";
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: Searching for Bundle in %ls registry", keyName);
|
||||
|
||||
HKEY uninstallKey;
|
||||
LONG openResult = RegOpenKeyExW(targetKey, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", 0, KEY_READ | KEY_ENUMERATE_SUB_KEYS, &uninstallKey);
|
||||
if (openResult != ERROR_SUCCESS)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: Failed to open uninstall key, error: %ld", openResult);
|
||||
goto LExit;
|
||||
}
|
||||
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize = sizeof(subKeyName) / sizeof(wchar_t);
|
||||
|
||||
while (RegEnumKeyExW(uninstallKey, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS)
|
||||
{
|
||||
HKEY productKey;
|
||||
if (RegOpenKeyExW(uninstallKey, subKeyName, 0, KEY_READ | KEY_WRITE, &productKey) == ERROR_SUCCESS)
|
||||
{
|
||||
wchar_t upgradeCode[256];
|
||||
DWORD upgradeCodeSize = sizeof(upgradeCode);
|
||||
DWORD valueType;
|
||||
|
||||
if (RegQueryValueExW(productKey, L"BundleUpgradeCode", nullptr, &valueType,
|
||||
reinterpret_cast<LPBYTE>(upgradeCode), &upgradeCodeSize) == ERROR_SUCCESS)
|
||||
{
|
||||
// Remove brackets from registry upgradeCode for comparison (bundleUpgradeCode doesn't have brackets)
|
||||
std::wstring regUpgradeCode = upgradeCode;
|
||||
if (!regUpgradeCode.empty() && regUpgradeCode.front() == L'{' && regUpgradeCode.back() == L'}')
|
||||
{
|
||||
regUpgradeCode = regUpgradeCode.substr(1, regUpgradeCode.length() - 2);
|
||||
}
|
||||
|
||||
if (_wcsicmp(regUpgradeCode.c_str(), bundleUpgradeCode.c_str()) == 0)
|
||||
{
|
||||
// Found matching Bundle, set InstallLocation
|
||||
LONG setResult = RegSetValueExW(productKey, L"InstallLocation", 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE*>(installationFolder.c_str()),
|
||||
static_cast<DWORD>((installationFolder.length() + 1) * sizeof(wchar_t)));
|
||||
|
||||
if (setResult == ERROR_SUCCESS)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: InstallLocation set successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: Failed to set InstallLocation, error: %ld", setResult);
|
||||
}
|
||||
|
||||
RegCloseKey(productKey);
|
||||
RegCloseKey(uninstallKey);
|
||||
goto LExit;
|
||||
}
|
||||
}
|
||||
RegCloseKey(productKey);
|
||||
}
|
||||
|
||||
index++;
|
||||
subKeyNameSize = sizeof(subKeyName) / sizeof(wchar_t);
|
||||
}
|
||||
|
||||
RegCloseKey(uninstallKey);
|
||||
|
||||
LExit:
|
||||
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
void initSystemLogger()
|
||||
{
|
||||
static std::once_flag initLoggerFlag;
|
||||
|
||||
@@ -32,4 +32,4 @@ EXPORTS
|
||||
CleanFileLocksmithRuntimeRegistryCA
|
||||
CleanPowerRenameRuntimeRegistryCA
|
||||
CleanNewPlusRuntimeRegistryCA
|
||||
|
||||
SetBundleInstallLocationCA
|
||||
|
||||
@@ -9,6 +9,25 @@
|
||||
<RegistryValue Type="string" Name="InstallScope" Value="$(var.InstallScope)" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<Component Id="powertoys_env_path_user" Bitness="always64">
|
||||
<!-- Anchor registry for component key path -->
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="powertoys_env_path_user" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Append install folder to current user's PATH -->
|
||||
<Environment Id="AddPowerToysToUserPath" Name="PATH" Action="set" Part="last" System="no" Value="[INSTALLFOLDER]" />
|
||||
</Component>
|
||||
<?else?>
|
||||
<Component Id="powertoys_env_path_machine" Bitness="always64">
|
||||
<!-- Anchor registry for component key path -->
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="powertoys_env_path_machine" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Append install folder to machine PATH -->
|
||||
<Environment Id="AddPowerToysToMachinePath" Name="PATH" Action="set" Part="last" System="yes" Value="[INSTALLFOLDER]" />
|
||||
</Component>
|
||||
<?endif?>
|
||||
<Component Id="powertoys_toast_clsid" Bitness="always64">
|
||||
<RemoveFolder Id="Remove_powertoys_toast_clsid" On="uninstall" />
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{DD5CACDA-7C2E-4997-A62A-04A597B58F76}">
|
||||
@@ -109,6 +128,11 @@
|
||||
<ComponentRef Id="powertoys_exe" />
|
||||
<ComponentRef Id="PowerToysStartMenuShortcut" />
|
||||
<ComponentRef Id="powertoys_per_machine_comp" />
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<ComponentRef Id="powertoys_env_path_user" />
|
||||
<?else?>
|
||||
<ComponentRef Id="powertoys_env_path_machine" />
|
||||
<?endif?>
|
||||
<ComponentRef Id="powertoys_toast_clsid" />
|
||||
<ComponentRef Id="License_rtf" />
|
||||
<ComponentRef Id="Notice_md" />
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
<Log Disable="no" Prefix="powertoys-bootstrapper-msi-$(var.Version)" Extension=".log" />
|
||||
|
||||
<!-- Store Bundle UpgradeCode for CustomAction -->
|
||||
<Variable Name="BundleUpgradeCode" Type="string" Value="$(var.UpgradeCode)" />
|
||||
|
||||
<!-- Only install/upgrade if the version is greater or equal than the currently installed version of PowerToys, to handle the case in which PowerToys was installed from old MSI (before WiX bootstrapper was used) -->
|
||||
<!-- If the previous installation is a bundle installation, just let WiX run its logic. -->
|
||||
<Variable Name="MinimumVersion" Type="version" Value="0.0.0.0" />
|
||||
@@ -58,6 +61,7 @@
|
||||
<MsiPackage DisplayName="PowerToys MSI" SourceFile="$(var.PowerToysPlatform)\Release\$(var.MSIPath)\$(var.MSIName)" Compressed="yes" bal:DisplayInternalUICondition="false">
|
||||
<MsiProperty Name="BOOTSTRAPPERINSTALLFOLDER" Value="[InstallFolder]" />
|
||||
<MsiProperty Name="MSIRESTARTMANAGERCONTROL" Value="Disable" />
|
||||
<MsiProperty Name="BUNDLEINFO" Value="[BundleUpgradeCode]" />
|
||||
</MsiPackage>
|
||||
</Chain>
|
||||
</Bundle>
|
||||
|
||||
@@ -14,6 +14,7 @@ SET PTRoot=$(SolutionDir)\..
|
||||
call "..\..\..\publish.cmd" x64
|
||||
)
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2"
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' != 'x64'">
|
||||
@@ -24,6 +25,7 @@ SET PTRoot=$(SolutionDir)\..
|
||||
call "..\..\..\publish.cmd" arm64
|
||||
)
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2"
|
||||
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@@ -115,6 +117,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
<Compile Include="KeyboardManager.wxs" />
|
||||
<Compile Include="Peek.wxs" />
|
||||
<Compile Include="PowerRename.wxs" />
|
||||
<Compile Include="DscResources.wxs" />
|
||||
<Compile Include="RegistryPreview.wxs" />
|
||||
<Compile Include="Run.wxs" />
|
||||
<Compile Include="Settings.wxs" />
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<ComponentGroupRef Id="NewPlusComponentGroup" />
|
||||
<ComponentGroupRef Id="NewPlusTemplatesComponentGroup" />
|
||||
<ComponentGroupRef Id="ResourcesComponentGroup" />
|
||||
<ComponentGroupRef Id="DscResourcesComponentGroup" />
|
||||
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
|
||||
<ComponentGroupRef Id="ToolComponentGroup" />
|
||||
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
||||
@@ -69,8 +70,8 @@
|
||||
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
||||
</Feature>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" Sequence="execute" />
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
|
||||
<UI>
|
||||
@@ -117,6 +118,8 @@
|
||||
|
||||
<Custom Action="SetUnApplyModulesRegistryChangeSetsParam" Before="UnApplyModulesRegistryChangeSets" />
|
||||
<Custom Action="CheckGPO" After="InstallInitialize" Condition="NOT Installed" />
|
||||
<Custom Action="SetBundleInstallLocationData" Before="SetBundleInstallLocation" Condition="NOT Installed" />
|
||||
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="InstallCmdPalPackage" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="override Wix4CloseApplications_$(sys.BUILDARCHSHORT)" Before="RemoveFiles" />
|
||||
@@ -160,6 +163,9 @@
|
||||
|
||||
<CustomAction Id="SetInstallCmdPalPackageParam" Property="InstallCmdPalPackage" Value="[INSTALLFOLDER]" />
|
||||
|
||||
<!-- Set InstallLocation for Bundle entry as well -->
|
||||
<CustomAction Id="SetBundleInstallLocationData" Property="SetBundleInstallLocation" Value="[INSTALLFOLDER];[BUNDLEINFO];[InstallScope]" />
|
||||
|
||||
<CustomAction Id="LaunchPowerToys" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="LaunchPowerToysCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="TerminateProcesses" Return="ignore" Execute="immediate" DllEntry="TerminateProcessesCA" BinaryRef="PTCustomActions" />
|
||||
@@ -244,6 +250,8 @@
|
||||
|
||||
<CustomAction Id="InstallCmdPalPackage" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallCmdPalPackageCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="SetBundleInstallLocation" Return="ignore" Impersonate="no" Execute="deferred" DllEntry="SetBundleInstallLocationCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<!-- Close 'PowerToys.exe' before uninstall-->
|
||||
<Property Id="MSIRESTARTMANAGERCONTROL" Value="DisableShutdown" />
|
||||
<Property Id="MSIFASTINSTALL" Value="DisableShutdown" />
|
||||
|
||||
102
installer/PowerToysSetupVNext/generateDscResourcesWxs.ps1
Normal file
102
installer/PowerToysSetupVNext/generateDscResourcesWxs.ps1
Normal file
@@ -0,0 +1,102 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string]$dscWxsFile,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string]$Platform,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string]$Configuration
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
# Find build output directory
|
||||
$buildOutputDir = Join-Path $scriptDir "..\..\$Platform\$Configuration"
|
||||
|
||||
if (-not (Test-Path $buildOutputDir)) {
|
||||
Write-Error "Build output directory not found: '$buildOutputDir'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find all DSC manifest JSON files
|
||||
$dscFiles = Get-ChildItem -Path $buildOutputDir -Filter "microsoft.powertoys.*.settings.dsc.resource.json" -File
|
||||
|
||||
if (-not $dscFiles) {
|
||||
Write-Warning "No DSC manifest files found in '$buildOutputDir'"
|
||||
# Create empty component group
|
||||
$wxsContent = @"
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
|
||||
<?include `$(sys.CURRENTDIR)\Common.wxi?>
|
||||
|
||||
<Fragment>
|
||||
<ComponentGroup Id="DscResourcesComponentGroup">
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
"@
|
||||
Set-Content -Path $dscWxsFile -Value $wxsContent
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "Found $($dscFiles.Count) DSC manifest file(s)"
|
||||
|
||||
# Generate WiX fragment
|
||||
$wxsContent = @"
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
|
||||
<?include `$(sys.CURRENTDIR)\Common.wxi?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="DSCModulesReferenceFolder">
|
||||
"@
|
||||
|
||||
$componentRefs = @()
|
||||
|
||||
foreach ($file in $dscFiles) {
|
||||
$componentId = "DscResource_" + ($file.BaseName -replace '[^A-Za-z0-9_]', '_')
|
||||
$fileId = $componentId + "_File"
|
||||
$guid = [System.Guid]::NewGuid().ToString().ToUpper()
|
||||
|
||||
$componentRefs += $componentId
|
||||
|
||||
$wxsContent += @"
|
||||
|
||||
<Component Id="$componentId" Guid="{$guid}" Directory="DSCModulesReferenceFolder">
|
||||
<RegistryKey Root="`$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="$componentId" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Id="$fileId" Source="`$(var.BinDir)$($file.Name)" Vital="no"/>
|
||||
</Component>
|
||||
"@
|
||||
}
|
||||
|
||||
$wxsContent += @"
|
||||
|
||||
</DirectoryRef>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<ComponentGroup Id="DscResourcesComponentGroup">
|
||||
"@
|
||||
|
||||
foreach ($componentId in $componentRefs) {
|
||||
$wxsContent += @"
|
||||
|
||||
<ComponentRef Id="$componentId"/>
|
||||
"@
|
||||
}
|
||||
|
||||
$wxsContent += @"
|
||||
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
"@
|
||||
|
||||
# Write the WiX file
|
||||
Set-Content -Path $dscWxsFile -Value $wxsContent
|
||||
|
||||
Write-Host "Generated DSC resources WiX fragment: '$dscWxsFile'"
|
||||
68
src/dsc/v3/PowerToys.DSC.UnitTests/BaseDscTest.cs
Normal file
68
src/dsc/v3/PowerToys.DSC.UnitTests/BaseDscTest.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Resources;
|
||||
using PowerToys.DSC.UnitTests.Models;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests;
|
||||
|
||||
public class BaseDscTest
|
||||
{
|
||||
private readonly ResourceManager _resourceManager;
|
||||
|
||||
public BaseDscTest()
|
||||
{
|
||||
_resourceManager = new ResourceManager("PowerToys.DSC.Properties.Resources", typeof(PowerToys.DSC.Program).Assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string resource for the given name, formatted with the provided arguments.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the resource string.</param>
|
||||
/// <param name="args">The arguments to format the resource string with.</param>
|
||||
/// <returns></returns>
|
||||
public string GetResourceString(string name, params string[] args)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, _resourceManager.GetString(name, CultureInfo.InvariantCulture), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a dsc command with the provided arguments.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
protected DscExecuteResult ExecuteDscCommand<T>(params string[] args)
|
||||
where T : Command, new()
|
||||
{
|
||||
var originalOut = Console.Out;
|
||||
var originalErr = Console.Error;
|
||||
|
||||
var outSw = new StringWriter();
|
||||
var errSw = new StringWriter();
|
||||
|
||||
try
|
||||
{
|
||||
Console.SetOut(outSw);
|
||||
Console.SetError(errSw);
|
||||
|
||||
var executeResult = new T().Invoke(args);
|
||||
var output = outSw.ToString();
|
||||
var errorOutput = errSw.ToString();
|
||||
return new(executeResult == 0, output, errorOutput);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Console.SetError(originalErr);
|
||||
outSw.Dispose();
|
||||
errSw.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/dsc/v3/PowerToys.DSC.UnitTests/CommandTest.cs
Normal file
37
src/dsc/v3/PowerToys.DSC.UnitTests/CommandTest.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerToys.DSC.Commands;
|
||||
using PowerToys.DSC.DSCResources;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class CommandTest : BaseDscTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetResource_Found_Success()
|
||||
{
|
||||
// Act
|
||||
var result = ExecuteDscCommand<GetCommand>("--resource", SettingsResource.ResourceName);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetResource_NotFound_Fail()
|
||||
{
|
||||
// Arrange
|
||||
var availableResources = string.Join(", ", BaseCommand.AvailableResources);
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<GetCommand>("--resource", "ResourceNotFound");
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.Contains(GetResourceString("InvalidResourceNameError", availableResources), result.Error);
|
||||
}
|
||||
}
|
||||
103
src/dsc/v3/PowerToys.DSC.UnitTests/Models/DscExecuteResult.cs
Normal file
103
src/dsc/v3/PowerToys.DSC.UnitTests/Models/DscExecuteResult.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using PowerToys.DSC.Models;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result of executing a DSC command.
|
||||
/// </summary>
|
||||
public class DscExecuteResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DscExecuteResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="success">Value indicating whether the command execution was successful.</param>
|
||||
/// <param name="output">Output stream content.</param>
|
||||
/// <param name="error">Error stream content.</param>
|
||||
public DscExecuteResult(bool success, string output, string error)
|
||||
{
|
||||
Success = success;
|
||||
Output = output;
|
||||
Error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command execution was successful.
|
||||
/// </summary>
|
||||
public bool Success { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output stream content of the operation.
|
||||
/// </summary>
|
||||
public string Output { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error stream content of the operation.
|
||||
/// </summary>
|
||||
public string Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the messages from the error stream.
|
||||
/// </summary>
|
||||
/// <returns>List of messages with their levels.</returns>
|
||||
public List<(DscMessageLevel Level, string Message)> Messages()
|
||||
{
|
||||
var lines = Error.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
|
||||
return lines.SelectMany(line =>
|
||||
{
|
||||
var map = JsonSerializer.Deserialize<Dictionary<string, string>>(line);
|
||||
return map.Select(v => (GetMessageLevel(v.Key), v.Value)).ToList();
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output as state.
|
||||
/// </summary>
|
||||
/// <returns>State.</returns>
|
||||
public T OutputState<T>()
|
||||
{
|
||||
var lines = Output.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
|
||||
Debug.Assert(lines.Length == 1, "Output should contain exactly one line.");
|
||||
return JsonSerializer.Deserialize<T>(lines[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output as state and diff.
|
||||
/// </summary>
|
||||
/// <returns>State and diff.</returns>
|
||||
public (T State, List<string> Diff) OutputStateAndDiff<T>()
|
||||
{
|
||||
var lines = Output.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
|
||||
Debug.Assert(lines.Length == 2, "Output should contain exactly two lines.");
|
||||
var obj = JsonSerializer.Deserialize<T>(lines[0]);
|
||||
var diff = JsonSerializer.Deserialize<List<string>>(lines[1]);
|
||||
return (obj, diff);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message level from a string representation.
|
||||
/// </summary>
|
||||
/// <param name="level">The string representation of the message level.</param>
|
||||
/// <returns>The level as <see cref="DscMessageLevel"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the level is unknown.</exception>
|
||||
private DscMessageLevel GetMessageLevel(string level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
"error" => DscMessageLevel.Error,
|
||||
"warn" => DscMessageLevel.Warning,
|
||||
"info" => DscMessageLevel.Info,
|
||||
"debug" => DscMessageLevel.Debug,
|
||||
"trace" => DscMessageLevel.Trace,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level), level, "Unknown message level"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<OutputPath>..\..\..\..\$(Configuration)\$(Platform)\tests\PowerToys.DSC.Tests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PowerToys.DSC\PowerToys.DSC.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceAdvancedPasteModuleTest : SettingsResourceModuleTest<AdvancedPasteSettings>
|
||||
{
|
||||
public SettingsResourceAdvancedPasteModuleTest()
|
||||
: base(nameof(ModuleType.AdvancedPaste))
|
||||
{
|
||||
}
|
||||
|
||||
protected override Action<AdvancedPasteSettings> GetSettingsModifier()
|
||||
{
|
||||
return s =>
|
||||
{
|
||||
s.Properties.ShowCustomPreview = !s.Properties.ShowCustomPreview;
|
||||
s.Properties.CloseAfterLosingFocus = !s.Properties.CloseAfterLosingFocus;
|
||||
s.Properties.IsAdvancedAIEnabled = !s.Properties.IsAdvancedAIEnabled;
|
||||
s.Properties.AdvancedPasteUIShortcut = new HotkeySettings
|
||||
{
|
||||
Key = "mock",
|
||||
Alt = true,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceAlwaysOnTopModuleTest : SettingsResourceModuleTest<AlwaysOnTopSettings>
|
||||
{
|
||||
public SettingsResourceAlwaysOnTopModuleTest()
|
||||
: base(nameof(ModuleType.AlwaysOnTop))
|
||||
{
|
||||
}
|
||||
|
||||
protected override Action<AlwaysOnTopSettings> GetSettingsModifier()
|
||||
{
|
||||
return s =>
|
||||
{
|
||||
s.Properties.RoundCornersEnabled.Value = !s.Properties.RoundCornersEnabled.Value;
|
||||
s.Properties.FrameEnabled.Value = !s.Properties.FrameEnabled.Value;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerToys.DSC.DSCResources;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceAppModuleTest : SettingsResourceModuleTest<GeneralSettings>
|
||||
{
|
||||
public SettingsResourceAppModuleTest()
|
||||
: base(SettingsResource.AppModule)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Action<GeneralSettings> GetSettingsModifier()
|
||||
{
|
||||
return s =>
|
||||
{
|
||||
s.Startup = !s.Startup;
|
||||
s.ShowSysTrayIcon = !s.ShowSysTrayIcon;
|
||||
s.Enabled.Awake = !s.Enabled.Awake;
|
||||
s.Enabled.ColorPicker = !s.Enabled.ColorPicker;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceAwakeModuleTest : SettingsResourceModuleTest<AwakeSettings>
|
||||
{
|
||||
public SettingsResourceAwakeModuleTest()
|
||||
: base(nameof(ModuleType.Awake))
|
||||
{
|
||||
}
|
||||
|
||||
protected override Action<AwakeSettings> GetSettingsModifier()
|
||||
{
|
||||
return s =>
|
||||
{
|
||||
s.Properties.ExpirationDateTime = DateTimeOffset.MinValue;
|
||||
s.Properties.IntervalHours = DefaultSettings.Properties.IntervalHours + 1;
|
||||
s.Properties.IntervalMinutes = DefaultSettings.Properties.IntervalMinutes + 1;
|
||||
s.Properties.Mode = s.Properties.Mode == AwakeMode.PASSIVE ? AwakeMode.TIMED : AwakeMode.PASSIVE;
|
||||
s.Properties.KeepDisplayOn = !s.Properties.KeepDisplayOn;
|
||||
s.Properties.CustomTrayTimes = new Dictionary<string, uint>
|
||||
{
|
||||
{ "08:00", 1 },
|
||||
{ "12:00", 2 },
|
||||
{ "16:00", 3 },
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceColorPickerModuleTest : SettingsResourceModuleTest<ColorPickerSettings>
|
||||
{
|
||||
public SettingsResourceColorPickerModuleTest()
|
||||
: base(nameof(ModuleType.ColorPicker))
|
||||
{
|
||||
}
|
||||
|
||||
protected override Action<ColorPickerSettings> GetSettingsModifier()
|
||||
{
|
||||
return s =>
|
||||
{
|
||||
s.Properties.ShowColorName = !s.Properties.ShowColorName;
|
||||
s.Properties.ColorHistoryLimit = s.Properties.ColorHistoryLimit == 0 ? 10 : 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ManagedCommon;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerToys.DSC.Commands;
|
||||
using PowerToys.DSC.DSCResources;
|
||||
using PowerToys.DSC.Models;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceCommandTest : BaseDscTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Modules_ListAllSupportedModules()
|
||||
{
|
||||
// Arrange
|
||||
var expectedModules = new List<string>()
|
||||
{
|
||||
SettingsResource.AppModule,
|
||||
nameof(ModuleType.AdvancedPaste),
|
||||
nameof(ModuleType.AlwaysOnTop),
|
||||
nameof(ModuleType.Awake),
|
||||
nameof(ModuleType.ColorPicker),
|
||||
nameof(ModuleType.CropAndLock),
|
||||
nameof(ModuleType.EnvironmentVariables),
|
||||
nameof(ModuleType.FancyZones),
|
||||
nameof(ModuleType.FileLocksmith),
|
||||
nameof(ModuleType.FindMyMouse),
|
||||
nameof(ModuleType.Hosts),
|
||||
nameof(ModuleType.ImageResizer),
|
||||
nameof(ModuleType.KeyboardManager),
|
||||
nameof(ModuleType.MouseHighlighter),
|
||||
nameof(ModuleType.MouseJump),
|
||||
nameof(ModuleType.MousePointerCrosshairs),
|
||||
nameof(ModuleType.Peek),
|
||||
nameof(ModuleType.PowerRename),
|
||||
nameof(ModuleType.PowerAccent),
|
||||
nameof(ModuleType.RegistryPreview),
|
||||
nameof(ModuleType.MeasureTool),
|
||||
nameof(ModuleType.ShortcutGuide),
|
||||
nameof(ModuleType.PowerOCR),
|
||||
nameof(ModuleType.Workspaces),
|
||||
nameof(ModuleType.ZoomIt),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<ModulesCommand>("--resource", SettingsResource.ResourceName);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(string.Join(Environment.NewLine, expectedModules.Order()), result.Output.Trim());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Set_EmptyInput_Fail()
|
||||
{
|
||||
// Act
|
||||
var result = ExecuteDscCommand<SetCommand>("--resource", SettingsResource.ResourceName, "--module", "Awake");
|
||||
var messages = result.Messages();
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(1, messages.Count);
|
||||
Assert.AreEqual(DscMessageLevel.Error, messages[0].Level);
|
||||
Assert.AreEqual(GetResourceString("InputEmptyOrNullError"), messages[0].Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_EmptyInput_Fail()
|
||||
{
|
||||
// Act
|
||||
var result = ExecuteDscCommand<TestCommand>("--resource", SettingsResource.ResourceName, "--module", "Awake");
|
||||
var messages = result.Messages();
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(1, messages.Count);
|
||||
Assert.AreEqual(DscMessageLevel.Error, messages[0].Level);
|
||||
Assert.AreEqual(GetResourceString("InputEmptyOrNullError"), messages[0].Message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class SettingsResourceCropAndLockModuleTest : SettingsResourceModuleTest<CropAndLockSettings>
|
||||
{
|
||||
public SettingsResourceCropAndLockModuleTest()
|
||||
: base(nameof(ModuleType.CropAndLock))
|
||||
{
|
||||
}
|
||||
|
||||
protected override Action<CropAndLockSettings> GetSettingsModifier()
|
||||
{
|
||||
return s =>
|
||||
{
|
||||
s.Properties.ThumbnailHotkey = new KeyboardKeysProperty()
|
||||
{
|
||||
Value = new HotkeySettings
|
||||
{
|
||||
Key = "mock",
|
||||
Alt = true,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerToys.DSC.Commands;
|
||||
using PowerToys.DSC.DSCResources;
|
||||
using PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
|
||||
public abstract class SettingsResourceModuleTest<TSettingsConfig> : BaseDscTest
|
||||
where TSettingsConfig : ISettingsConfig, new()
|
||||
{
|
||||
private readonly SettingsUtils _settingsUtils = new();
|
||||
private TSettingsConfig _originalSettings;
|
||||
|
||||
protected TSettingsConfig DefaultSettings => new();
|
||||
|
||||
protected string Module { get; }
|
||||
|
||||
protected List<string> DiffSettings { get; } = [SettingsResourceObject<AwakeSettings>.SettingsJsonPropertyName];
|
||||
|
||||
protected List<string> DiffEmpty { get; } = [];
|
||||
|
||||
public SettingsResourceModuleTest(string module)
|
||||
{
|
||||
Module = module;
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_originalSettings = GetSettings();
|
||||
ResetSettingsToDefaultValues();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
SaveSettings(_originalSettings);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_Success()
|
||||
{
|
||||
// Arrange
|
||||
var settingsBeforeExecute = GetSettings();
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<GetCommand>("--resource", SettingsResource.ResourceName, "--module", Module);
|
||||
var state = result.OutputState<SettingsResourceObject<TSettingsConfig>>();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
AssertSettingsAreEqual(settingsBeforeExecute, GetSettings());
|
||||
AssertStateAndSettingsAreEqual(settingsBeforeExecute, state);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Export_Success()
|
||||
{
|
||||
// Arrange
|
||||
var settingsBeforeExecute = GetSettings();
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<ExportCommand>("--resource", SettingsResource.ResourceName, "--module", Module);
|
||||
var state = result.OutputState<SettingsResourceObject<TSettingsConfig>>();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
AssertSettingsAreEqual(settingsBeforeExecute, GetSettings());
|
||||
AssertStateAndSettingsAreEqual(settingsBeforeExecute, state);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetWithDiff_Success()
|
||||
{
|
||||
// Arrange
|
||||
var settingsModifier = GetSettingsModifier();
|
||||
var input = CreateInputResourceObject(settingsModifier);
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<SetCommand>("--resource", SettingsResource.ResourceName, "--module", Module, "--input", input);
|
||||
var (state, diff) = result.OutputStateAndDiff<SettingsResourceObject<TSettingsConfig>>();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
AssertSettingsHasChanged(settingsModifier);
|
||||
AssertStateAndSettingsAreEqual(GetSettings(), state);
|
||||
CollectionAssert.AreEqual(DiffSettings, diff);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetWithoutDiff_Success()
|
||||
{
|
||||
// Arrange
|
||||
var settingsModifier = GetSettingsModifier();
|
||||
UpdateSettings(settingsModifier);
|
||||
var settingsBeforeExecute = GetSettings();
|
||||
var input = CreateInputResourceObject(settingsModifier);
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<SetCommand>("--resource", SettingsResource.ResourceName, "--module", Module, "--input", input);
|
||||
var (state, diff) = result.OutputStateAndDiff<SettingsResourceObject<TSettingsConfig>>();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
AssertSettingsAreEqual(settingsBeforeExecute, GetSettings());
|
||||
AssertStateAndSettingsAreEqual(settingsBeforeExecute, state);
|
||||
CollectionAssert.AreEqual(DiffEmpty, diff);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestWithDiff_Success()
|
||||
{
|
||||
// Arrange
|
||||
var settingsModifier = GetSettingsModifier();
|
||||
var settingsBeforeExecute = GetSettings();
|
||||
var input = CreateInputResourceObject(settingsModifier);
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<TestCommand>("--resource", SettingsResource.ResourceName, "--module", Module, "--input", input);
|
||||
var (state, diff) = result.OutputStateAndDiff<SettingsResourceObject<TSettingsConfig>>();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
AssertSettingsAreEqual(settingsBeforeExecute, GetSettings());
|
||||
AssertStateAndSettingsAreEqual(settingsBeforeExecute, state);
|
||||
CollectionAssert.AreEqual(DiffSettings, diff);
|
||||
Assert.IsFalse(state.InDesiredState);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestWithoutDiff_Success()
|
||||
{
|
||||
// Arrange
|
||||
var settingsModifier = GetSettingsModifier();
|
||||
UpdateSettings(settingsModifier);
|
||||
var settingsBeforeExecute = GetSettings();
|
||||
var input = CreateInputResourceObject(settingsModifier);
|
||||
|
||||
// Act
|
||||
var result = ExecuteDscCommand<TestCommand>("--resource", SettingsResource.ResourceName, "--module", Module, "--input", input);
|
||||
var (state, diff) = result.OutputStateAndDiff<SettingsResourceObject<TSettingsConfig>>();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Success);
|
||||
AssertSettingsAreEqual(settingsBeforeExecute, GetSettings());
|
||||
AssertStateAndSettingsAreEqual(settingsBeforeExecute, state);
|
||||
CollectionAssert.AreEqual(DiffEmpty, diff);
|
||||
Assert.IsTrue(state.InDesiredState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings modifier action for the specific settings configuration.
|
||||
/// </summary>
|
||||
/// <returns>An action that modifies the settings configuration.</returns>
|
||||
protected abstract Action<TSettingsConfig> GetSettingsModifier();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the settings to default values.
|
||||
/// </summary>
|
||||
private void ResetSettingsToDefaultValues()
|
||||
{
|
||||
SaveSettings(DefaultSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the settings for the specified module.
|
||||
/// </summary>
|
||||
/// <returns>An instance of the settings type with the current configuration.</returns>
|
||||
private TSettingsConfig GetSettings()
|
||||
{
|
||||
return _settingsUtils.GetSettingsOrDefault<TSettingsConfig>(DefaultSettings.GetModuleName());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the settings for the specified module.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to save.</param>
|
||||
private void SaveSettings(TSettingsConfig settings)
|
||||
{
|
||||
_settingsUtils.SaveSettings(JsonSerializer.Serialize(settings), DefaultSettings.GetModuleName());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the resource object for the operation.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to include in the resource object.</param>
|
||||
/// <returns>A JSON string representing the resource object.</returns>
|
||||
private string CreateResourceObject(TSettingsConfig settings)
|
||||
{
|
||||
var resourceObject = new SettingsResourceObject<TSettingsConfig>
|
||||
{
|
||||
Settings = settings,
|
||||
};
|
||||
return JsonSerializer.Serialize(resourceObject);
|
||||
}
|
||||
|
||||
private string CreateInputResourceObject(Action<TSettingsConfig> settingsModifier)
|
||||
{
|
||||
var settings = DefaultSettings;
|
||||
settingsModifier(settings);
|
||||
return CreateResourceObject(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the response for the Get operation.
|
||||
/// </summary>
|
||||
/// <returns>A JSON string representing the response.</returns>
|
||||
private string CreateGetResponse()
|
||||
{
|
||||
return CreateResourceObject(GetSettings());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the state and settings are equal.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings manifest to compare against.</param>
|
||||
/// <param name="state">Output state to compare.</param>
|
||||
private void AssertStateAndSettingsAreEqual(TSettingsConfig settings, SettingsResourceObject<TSettingsConfig> state)
|
||||
{
|
||||
AssertSettingsAreEqual(settings, state.Settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that two settings manifests are equal.
|
||||
/// </summary>
|
||||
/// <param name="expected">Expected settings.</param>
|
||||
/// <param name="actual">Actual settings.</param>
|
||||
private void AssertSettingsAreEqual(TSettingsConfig expected, TSettingsConfig actual)
|
||||
{
|
||||
var expectedJson = JsonSerializer.SerializeToNode(expected) as JsonObject;
|
||||
var actualJson = JsonSerializer.SerializeToNode(actual) as JsonObject;
|
||||
Assert.IsTrue(JsonNode.DeepEquals(expectedJson, actualJson));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the current settings have changed.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to prepare the default settings.</param>
|
||||
private void AssertSettingsHasChanged(Action<TSettingsConfig> action)
|
||||
{
|
||||
var currentSettings = GetSettings();
|
||||
var defaultSettings = DefaultSettings;
|
||||
action(defaultSettings);
|
||||
AssertSettingsAreEqual(defaultSettings, currentSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the settings.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to modify the settings.</param>
|
||||
private void UpdateSettings(Action<TSettingsConfig> action)
|
||||
{
|
||||
var settings = GetSettings();
|
||||
action(settings);
|
||||
SaveSettings(settings);
|
||||
}
|
||||
}
|
||||
120
src/dsc/v3/PowerToys.DSC/Commands/BaseCommand.cs
Normal file
120
src/dsc/v3/PowerToys.DSC/Commands/BaseCommand.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.CommandLine.IO;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using PowerToys.DSC.DSCResources;
|
||||
using PowerToys.DSC.Options;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all DSC commands.
|
||||
/// </summary>
|
||||
public abstract class BaseCommand : Command
|
||||
{
|
||||
private static readonly CompositeFormat ModuleNotSupportedByResource = CompositeFormat.Parse(Resources.ModuleNotSupportedByResource);
|
||||
|
||||
// Shared options for all commands
|
||||
private readonly ModuleOption _moduleOption;
|
||||
private readonly ResourceOption _resourceOption;
|
||||
private readonly InputOption _inputOption;
|
||||
|
||||
// The dictionary of available resources and their factories.
|
||||
private static readonly Dictionary<string, Func<string?, BaseResource>> _resourceFactories = new()
|
||||
{
|
||||
{ SettingsResource.ResourceName, module => new SettingsResource(module) },
|
||||
|
||||
// Add other resources here
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of available DSC resources that can be used with the command.
|
||||
/// </summary>
|
||||
public static List<string> AvailableResources => [.._resourceFactories.Keys];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DSC resource to be used by the command.
|
||||
/// </summary>
|
||||
protected BaseResource? Resource { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input JSON provided by the user.
|
||||
/// </summary>
|
||||
protected string? Input { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PowerToys module to be used by the command.
|
||||
/// </summary>
|
||||
protected string? Module { get; private set; }
|
||||
|
||||
public BaseCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
// Register the common options for all commands
|
||||
_moduleOption = new ModuleOption();
|
||||
AddOption(_moduleOption);
|
||||
|
||||
_resourceOption = new ResourceOption(AvailableResources);
|
||||
AddOption(_resourceOption);
|
||||
|
||||
_inputOption = new InputOption();
|
||||
AddOption(_inputOption);
|
||||
|
||||
// Register the command handler
|
||||
this.SetHandler(CommandHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the command invocation.
|
||||
/// </summary>
|
||||
/// <param name="context">The invocation context containing the parsed command options.</param>
|
||||
public void CommandHandler(InvocationContext context)
|
||||
{
|
||||
Input = context.ParseResult.GetValueForOption(_inputOption);
|
||||
Module = context.ParseResult.GetValueForOption(_moduleOption);
|
||||
Resource = ResolvedResource(context);
|
||||
|
||||
// Validate the module against the resource's supported modules
|
||||
var supportedModules = Resource.GetSupportedModules();
|
||||
if (!string.IsNullOrEmpty(Module) && !supportedModules.Contains(Module))
|
||||
{
|
||||
var errorMessage = string.Format(CultureInfo.InvariantCulture, ModuleNotSupportedByResource, Module, Resource.Name);
|
||||
context.Console.Error.WriteLine(errorMessage);
|
||||
context.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue with the command handler logic
|
||||
CommandHandlerInternal(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the command logic internally.
|
||||
/// </summary>
|
||||
/// <param name="context">Invocation context containing the parsed command options.</param>
|
||||
public abstract void CommandHandlerInternal(InvocationContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the resource from the provided resource name in the context.
|
||||
/// </summary>
|
||||
/// <param name="context">Invocation context containing the parsed command options.</param>
|
||||
/// <returns>The resolved <see cref="BaseResource"/> instance.</returns>
|
||||
private BaseResource ResolvedResource(InvocationContext context)
|
||||
{
|
||||
// Resource option has already been validated before the command
|
||||
// handler is invoked.
|
||||
var resourceName = context.ParseResult.GetValueForOption(_resourceOption);
|
||||
Debug.Assert(!string.IsNullOrEmpty(resourceName), "Resource name must not be null or empty.");
|
||||
Debug.Assert(_resourceFactories.ContainsKey(resourceName), $"Resource '{resourceName}' is not registered.");
|
||||
return _resourceFactories[resourceName](Module);
|
||||
}
|
||||
}
|
||||
25
src/dsc/v3/PowerToys.DSC/Commands/ExportCommand.cs
Normal file
25
src/dsc/v3/PowerToys.DSC/Commands/ExportCommand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine.Invocation;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to export all state instances.
|
||||
/// </summary>
|
||||
public sealed class ExportCommand : BaseCommand
|
||||
{
|
||||
public ExportCommand()
|
||||
: base("export", Resources.ExportCommandDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
context.ExitCode = Resource!.ExportState(Input) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
25
src/dsc/v3/PowerToys.DSC/Commands/GetCommand.cs
Normal file
25
src/dsc/v3/PowerToys.DSC/Commands/GetCommand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine.Invocation;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to get the resource state.
|
||||
/// </summary>
|
||||
public sealed class GetCommand : BaseCommand
|
||||
{
|
||||
public GetCommand()
|
||||
: base("get", Resources.GetCommandDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
context.ExitCode = Resource!.GetState(Input) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
34
src/dsc/v3/PowerToys.DSC/Commands/ManifestCommand.cs
Normal file
34
src/dsc/v3/PowerToys.DSC/Commands/ManifestCommand.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine.Invocation;
|
||||
using PowerToys.DSC.Options;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to get the manifest of the DSC resource.
|
||||
/// </summary>
|
||||
public sealed class ManifestCommand : BaseCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Option to specify the output directory for the manifest.
|
||||
/// </summary>
|
||||
private readonly OutputDirectoryOption _outputDirectoryOption;
|
||||
|
||||
public ManifestCommand()
|
||||
: base("manifest", Resources.ManifestCommandDescription)
|
||||
{
|
||||
_outputDirectoryOption = new OutputDirectoryOption();
|
||||
AddOption(_outputDirectoryOption);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
var outputDir = context.ParseResult.GetValueForOption(_outputDirectoryOption);
|
||||
context.ExitCode = Resource!.Manifest(outputDir) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
47
src/dsc/v3/PowerToys.DSC/Commands/ModulesCommand.cs
Normal file
47
src/dsc/v3/PowerToys.DSC/Commands/ModulesCommand.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.Diagnostics;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to get all supported modules for a specific resource.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is primarily used for debugging purposes and for build scripts.
|
||||
/// </remarks>
|
||||
public sealed class ModulesCommand : BaseCommand
|
||||
{
|
||||
public ModulesCommand()
|
||||
: base("modules", Resources.ModulesCommandDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
// Module is optional, if not provided, all supported modules for the
|
||||
// resource will be printed. If provided, it must be one of the
|
||||
// supported modules since it has been validated before this command is
|
||||
// executed.
|
||||
if (!string.IsNullOrEmpty(Module))
|
||||
{
|
||||
Debug.Assert(Resource!.GetSupportedModules().Contains(Module), "Module must be present in the list of supported modules.");
|
||||
context.Console.WriteLine(Module);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Print the supported modules for the specified resource
|
||||
foreach (var module in Resource!.GetSupportedModules())
|
||||
{
|
||||
context.Console.WriteLine(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/dsc/v3/PowerToys.DSC/Commands/SchemaCommand.cs
Normal file
25
src/dsc/v3/PowerToys.DSC/Commands/SchemaCommand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine.Invocation;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to output the schema of the resource.
|
||||
/// </summary>
|
||||
public sealed class SchemaCommand : BaseCommand
|
||||
{
|
||||
public SchemaCommand()
|
||||
: base("schema", Resources.SchemaCommandDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
context.ExitCode = Resource!.Schema() ? 0 : 1;
|
||||
}
|
||||
}
|
||||
25
src/dsc/v3/PowerToys.DSC/Commands/SetCommand.cs
Normal file
25
src/dsc/v3/PowerToys.DSC/Commands/SetCommand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine.Invocation;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to set the resource state.
|
||||
/// </summary>
|
||||
public sealed class SetCommand : BaseCommand
|
||||
{
|
||||
public SetCommand()
|
||||
: base("set", Resources.SetCommandDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
context.ExitCode = Resource!.SetState(Input) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
25
src/dsc/v3/PowerToys.DSC/Commands/TestCommand.cs
Normal file
25
src/dsc/v3/PowerToys.DSC/Commands/TestCommand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine.Invocation;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to test the resource state.
|
||||
/// </summary>
|
||||
public sealed class TestCommand : BaseCommand
|
||||
{
|
||||
public TestCommand()
|
||||
: base("test", Resources.TestCommandDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CommandHandlerInternal(InvocationContext context)
|
||||
{
|
||||
context.ExitCode = Resource!.TestState(Input) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
134
src/dsc/v3/PowerToys.DSC/DSCResources/BaseResource.cs
Normal file
134
src/dsc/v3/PowerToys.DSC/DSCResources/BaseResource.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Nodes;
|
||||
using PowerToys.DSC.Models;
|
||||
|
||||
namespace PowerToys.DSC.DSCResources;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all DSC resources.
|
||||
/// </summary>
|
||||
public abstract class BaseResource
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the resource.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module being used by the resource, if provided.
|
||||
/// </summary>
|
||||
public string? Module { get; }
|
||||
|
||||
public BaseResource(string name, string? module)
|
||||
{
|
||||
Name = name;
|
||||
Module = module;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the get method on the resource.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string, if any.</param>
|
||||
/// <returns>True if the operation was successful; otherwise false.</returns>
|
||||
public abstract bool GetState(string? input);
|
||||
|
||||
/// <summary>
|
||||
/// Calls the set method on the resource.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string, if any.</param>
|
||||
/// <returns>True if the operation was successful; otherwise false.</returns>
|
||||
public abstract bool SetState(string? input);
|
||||
|
||||
/// <summary>
|
||||
/// Calls the test method on the resource.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string, if any.</param>
|
||||
/// <returns>True if the operation was successful; otherwise false.</returns>
|
||||
public abstract bool TestState(string? input);
|
||||
|
||||
/// <summary>
|
||||
/// Calls the export method on the resource.
|
||||
/// </summary>
|
||||
/// <param name="input"> The input string, if any.</param>
|
||||
/// <returns>True if the operation was successful; otherwise false.</returns>
|
||||
public abstract bool ExportState(string? input);
|
||||
|
||||
/// <summary>
|
||||
/// Calls the schema method on the resource.
|
||||
/// </summary>
|
||||
/// <returns>True if the operation was successful; otherwise false.</returns>
|
||||
public abstract bool Schema();
|
||||
|
||||
/// <summary>
|
||||
/// Generates a DSC resource JSON manifest for the resource. If the
|
||||
/// outputDir is not provided, the manifest will be printed to the console.
|
||||
/// </summary>
|
||||
/// <param name="outputDir"> The directory where the manifest should be
|
||||
/// saved. If null, the manifest will be printed to the console.</param>
|
||||
/// <returns>True if the manifest was successfully generated and saved,otherwise false.</returns>
|
||||
public abstract bool Manifest(string? outputDir);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of supported modules for the resource.
|
||||
/// </summary>
|
||||
/// <returns>Gets a list of supported modules.</returns>
|
||||
public abstract IList<string> GetSupportedModules();
|
||||
|
||||
/// <summary>
|
||||
/// Writes a JSON output line to the console.
|
||||
/// </summary>
|
||||
/// <param name="output">The JSON output to write.</param>
|
||||
protected void WriteJsonOutputLine(JsonNode output)
|
||||
{
|
||||
var json = output.ToJsonString(new() { WriteIndented = false });
|
||||
WriteJsonOutputLine(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a JSON output line to the console.
|
||||
/// </summary>
|
||||
/// <param name="output">The JSON output to write.</param>
|
||||
protected void WriteJsonOutputLine(string output)
|
||||
{
|
||||
Console.WriteLine(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a message output line to the console with the specified message level.
|
||||
/// </summary>
|
||||
/// <param name="level">The level of the message.</param>
|
||||
/// <param name="message">The message to write.</param>
|
||||
protected void WriteMessageOutputLine(DscMessageLevel level, string message)
|
||||
{
|
||||
var messageObj = new Dictionary<string, string>
|
||||
{
|
||||
[GetMessageLevel(level)] = message,
|
||||
};
|
||||
var messageJson = System.Text.Json.JsonSerializer.Serialize(messageObj);
|
||||
Console.Error.WriteLine(messageJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message level as a string based on the provided dsc message level enum value.
|
||||
/// </summary>
|
||||
/// <param name="level">The dsc message level.</param>
|
||||
/// <returns>A string representation of the message level.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the provided message level is not recognized.</exception>
|
||||
private static string GetMessageLevel(DscMessageLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
DscMessageLevel.Error => "error",
|
||||
DscMessageLevel.Warning => "warn",
|
||||
DscMessageLevel.Info => "info",
|
||||
DscMessageLevel.Debug => "debug",
|
||||
DscMessageLevel.Trace => "trace",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
248
src/dsc/v3/PowerToys.DSC/DSCResources/SettingsResource.cs
Normal file
248
src/dsc/v3/PowerToys.DSC/DSCResources/SettingsResource.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using PowerToys.DSC.Models;
|
||||
using PowerToys.DSC.Models.FunctionData;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.DSCResources;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the DSC resource for managing PowerToys settings.
|
||||
/// </summary>
|
||||
public sealed class SettingsResource : BaseResource
|
||||
{
|
||||
private static readonly CompositeFormat FailedToWriteManifests = CompositeFormat.Parse(Resources.FailedToWriteManifests);
|
||||
|
||||
public const string AppModule = "App";
|
||||
public const string ResourceName = "settings";
|
||||
|
||||
private readonly Dictionary<string, Func<string?, ISettingsFunctionData>> _moduleFunctionData;
|
||||
|
||||
public string ModuleOrDefault => string.IsNullOrEmpty(Module) ? AppModule : Module;
|
||||
|
||||
public SettingsResource(string? module)
|
||||
: base(ResourceName, module)
|
||||
{
|
||||
_moduleFunctionData = new()
|
||||
{
|
||||
{ AppModule, CreateModuleFunctionData<GeneralSettings> },
|
||||
{ nameof(ModuleType.AdvancedPaste), CreateModuleFunctionData<AdvancedPasteSettings> },
|
||||
{ nameof(ModuleType.AlwaysOnTop), CreateModuleFunctionData<AlwaysOnTopSettings> },
|
||||
{ nameof(ModuleType.Awake), CreateModuleFunctionData<AwakeSettings> },
|
||||
{ nameof(ModuleType.ColorPicker), CreateModuleFunctionData<ColorPickerSettings> },
|
||||
{ nameof(ModuleType.CropAndLock), CreateModuleFunctionData<CropAndLockSettings> },
|
||||
{ nameof(ModuleType.EnvironmentVariables), CreateModuleFunctionData<EnvironmentVariablesSettings> },
|
||||
{ nameof(ModuleType.FancyZones), CreateModuleFunctionData<FancyZonesSettings> },
|
||||
{ nameof(ModuleType.FileLocksmith), CreateModuleFunctionData<FileLocksmithSettings> },
|
||||
{ nameof(ModuleType.FindMyMouse), CreateModuleFunctionData<FindMyMouseSettings> },
|
||||
{ nameof(ModuleType.Hosts), CreateModuleFunctionData<HostsSettings> },
|
||||
{ nameof(ModuleType.ImageResizer), CreateModuleFunctionData<ImageResizerSettings> },
|
||||
{ nameof(ModuleType.KeyboardManager), CreateModuleFunctionData<KeyboardManagerSettings> },
|
||||
{ nameof(ModuleType.MouseHighlighter), CreateModuleFunctionData<MouseHighlighterSettings> },
|
||||
{ nameof(ModuleType.MouseJump), CreateModuleFunctionData<MouseJumpSettings> },
|
||||
{ nameof(ModuleType.MousePointerCrosshairs), CreateModuleFunctionData<MousePointerCrosshairsSettings> },
|
||||
{ nameof(ModuleType.Peek), CreateModuleFunctionData<PeekSettings> },
|
||||
{ nameof(ModuleType.PowerRename), CreateModuleFunctionData<PowerRenameSettings> },
|
||||
{ nameof(ModuleType.PowerAccent), CreateModuleFunctionData<PowerAccentSettings> },
|
||||
{ nameof(ModuleType.RegistryPreview), CreateModuleFunctionData<RegistryPreviewSettings> },
|
||||
{ nameof(ModuleType.MeasureTool), CreateModuleFunctionData<MeasureToolSettings> },
|
||||
{ nameof(ModuleType.ShortcutGuide), CreateModuleFunctionData<ShortcutGuideSettings> },
|
||||
{ nameof(ModuleType.PowerOCR), CreateModuleFunctionData<PowerOcrSettings> },
|
||||
{ nameof(ModuleType.Workspaces), CreateModuleFunctionData<WorkspacesSettings> },
|
||||
{ nameof(ModuleType.ZoomIt), CreateModuleFunctionData<ZoomItSettings> },
|
||||
|
||||
// The following modules are not currently supported:
|
||||
// - MouseWithoutBorders Contains sensitive configuration values, making export/import potentially insecure.
|
||||
// - PowerLauncher Uses absolute file paths in its settings, which are not portable across systems.
|
||||
// - NewPlus Uses absolute file paths in its settings, which are not portable across systems.
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ExportState(string? input)
|
||||
{
|
||||
var data = CreateFunctionData();
|
||||
data.GetState();
|
||||
WriteJsonOutputLine(data.Output.ToJson());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool GetState(string? input)
|
||||
{
|
||||
return ExportState(input);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool SetState(string? input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
WriteMessageOutputLine(DscMessageLevel.Error, Resources.InputEmptyOrNullError);
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = CreateFunctionData(input);
|
||||
data.GetState();
|
||||
|
||||
// Capture the diff before updating the output
|
||||
var diff = data.GetDiffJson();
|
||||
|
||||
// Only call Set if the desired state is different from the current state
|
||||
if (!data.TestState())
|
||||
{
|
||||
var inputSettings = data.Input.SettingsInternal;
|
||||
data.Output.SettingsInternal = inputSettings;
|
||||
data.SetState();
|
||||
}
|
||||
|
||||
WriteJsonOutputLine(data.Output.ToJson());
|
||||
WriteJsonOutputLine(diff);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool TestState(string? input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
WriteMessageOutputLine(DscMessageLevel.Error, Resources.InputEmptyOrNullError);
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = CreateFunctionData(input);
|
||||
data.GetState();
|
||||
data.Output.InDesiredState = data.TestState();
|
||||
|
||||
WriteJsonOutputLine(data.Output.ToJson());
|
||||
WriteJsonOutputLine(data.GetDiffJson());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Schema()
|
||||
{
|
||||
var data = CreateFunctionData();
|
||||
WriteJsonOutputLine(data.Schema());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// If an output directory is specified, write the manifests to files,
|
||||
/// otherwise output them to the console.
|
||||
/// </remarks>
|
||||
public override bool Manifest(string? outputDir)
|
||||
{
|
||||
var manifests = GenerateManifests();
|
||||
|
||||
if (!string.IsNullOrEmpty(outputDir))
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var (name, manifest) in manifests)
|
||||
{
|
||||
File.WriteAllText(Path.Combine(outputDir, $"microsoft.powertoys.{name}.settings.dsc.resource.json"), manifest);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = string.Format(CultureInfo.InvariantCulture, FailedToWriteManifests, outputDir, ex.Message);
|
||||
WriteMessageOutputLine(DscMessageLevel.Error, errorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var (_, manifest) in manifests)
|
||||
{
|
||||
WriteJsonOutputLine(manifest);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates manifests for the specified module or all supported modules
|
||||
/// if no module is specified.
|
||||
/// </summary>
|
||||
/// <returns>A list of tuples containing the module name and its corresponding manifest JSON.</returns>
|
||||
private List<(string Name, string Manifest)> GenerateManifests()
|
||||
{
|
||||
List<(string Name, string Manifest)> manifests = [];
|
||||
if (!string.IsNullOrEmpty(Module))
|
||||
{
|
||||
manifests.Add((Module, GenerateManifest(Module)));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var module in GetSupportedModules())
|
||||
{
|
||||
manifests.Add((module, GenerateManifest(module)));
|
||||
}
|
||||
}
|
||||
|
||||
return manifests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a DSC resource JSON manifest for the specified module.
|
||||
/// </summary>
|
||||
/// <param name="module">The name of the module for which to generate the manifest.</param>
|
||||
/// <returns>A JSON string representing the DSC resource manifest.</returns>
|
||||
private string GenerateManifest(string module)
|
||||
{
|
||||
// Note: The description is not localized because the generated
|
||||
// manifest file will be part of the package
|
||||
return new DscManifest($"{module}Settings", "0.1.0")
|
||||
.AddDescription($"Allows management of {module} settings state via the DSC v3 command line interface protocol.")
|
||||
.AddStdinMethod("export", ["export", "--module", module, "--resource", "settings"])
|
||||
.AddStdinMethod("get", ["get", "--module", module, "--resource", "settings"])
|
||||
.AddJsonInputMethod("set", "--input", ["set", "--module", module, "--resource", "settings"], implementsPretest: true, stateAndDiff: true)
|
||||
.AddJsonInputMethod("test", "--input", ["test", "--module", module, "--resource", "settings"], stateAndDiff: true)
|
||||
.AddCommandMethod("schema", ["schema", "--module", module, "--resource", "settings"])
|
||||
.ToJson();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IList<string> GetSupportedModules()
|
||||
{
|
||||
return [.. _moduleFunctionData.Keys.Order()];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the function data for the specified module or the default module if none is specified.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string, if any.</param>
|
||||
/// <returns>An instance of <see cref="ISettingsFunctionData"/> for the specified module.</returns>
|
||||
public ISettingsFunctionData CreateFunctionData(string? input = null)
|
||||
{
|
||||
Debug.Assert(_moduleFunctionData.ContainsKey(ModuleOrDefault), "Module should be supported by the resource.");
|
||||
return _moduleFunctionData[ModuleOrDefault](input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the function data for a specific settings configuration type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSettingsConfig">The type of settings configuration to create function data for.</typeparam>
|
||||
/// <param name="input">The input string, if any.</param>
|
||||
/// <returns>An instance of <see cref="ISettingsFunctionData"/> for the specified settings configuration type.</returns>
|
||||
private ISettingsFunctionData CreateModuleFunctionData<TSettingsConfig>(string? input)
|
||||
where TSettingsConfig : ISettingsConfig, new()
|
||||
{
|
||||
return new SettingsFunctionData<TSettingsConfig>(input);
|
||||
}
|
||||
}
|
||||
152
src/dsc/v3/PowerToys.DSC/Models/DscManifest.cs
Normal file
152
src/dsc/v3/PowerToys.DSC/Models/DscManifest.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace PowerToys.DSC.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Class for building a DSC manifest for PowerToys resources.
|
||||
/// </summary>
|
||||
public sealed class DscManifest
|
||||
{
|
||||
private const string Schema = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json";
|
||||
private const string Executable = @"PowerToys.DSC.exe";
|
||||
|
||||
private readonly string _type;
|
||||
private readonly string _version;
|
||||
private readonly JsonObject _manifest;
|
||||
|
||||
public DscManifest(string type, string version)
|
||||
{
|
||||
_type = type;
|
||||
_version = version;
|
||||
_manifest = new JsonObject
|
||||
{
|
||||
["$schema"] = Schema,
|
||||
["type"] = $"Microsoft.PowerToys/{_type}",
|
||||
["version"] = _version,
|
||||
["tags"] = new JsonArray("PowerToys"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a description to the manifest.
|
||||
/// </summary>
|
||||
/// <param name="description">The description to add.</param>
|
||||
/// <returns>Returns the current instance of <see cref="DscManifest"/>.</returns>
|
||||
public DscManifest AddDescription(string description)
|
||||
{
|
||||
_manifest["description"] = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a method to the manifest with the specified executable and arguments.
|
||||
/// </summary>
|
||||
/// <param name="method">The name of the method to add.</param>
|
||||
/// <param name="inputArg">The input argument for the method</param>
|
||||
/// <param name="args">The list of arguments for the method.</param>
|
||||
/// <param name="implementsPretest">Whether the method implements a pretest.</param>
|
||||
/// <param name="stateAndDiff">Whether the method returns state and diff.</param>
|
||||
/// <returns>Returns the current instance of <see cref="DscManifest"/>.</returns>
|
||||
public DscManifest AddJsonInputMethod(string method, string inputArg, List<string> args, bool? implementsPretest = null, bool? stateAndDiff = null)
|
||||
{
|
||||
var argsJson = CreateJsonArray(args);
|
||||
argsJson.Add(new JsonObject
|
||||
{
|
||||
["jsonInputArg"] = inputArg,
|
||||
["mandatory"] = true,
|
||||
});
|
||||
var methodObject = AddMethod(argsJson, implementsPretest, stateAndDiff);
|
||||
_manifest[method] = methodObject;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a method to the manifest that reads from standard input (stdin).
|
||||
/// </summary>
|
||||
/// <param name="method">The name of the method to add.</param>
|
||||
/// <param name="args">The list of arguments for the method.</param>
|
||||
/// <param name="implementsPretest">Whether the method implements a pretest.</param>
|
||||
/// <param name="stateAndDiff">Whether the method returns state and diff.</param>
|
||||
/// <returns>Returns the current instance of <see cref="DscManifest"/>.</returns>
|
||||
public DscManifest AddStdinMethod(string method, List<string> args, bool? implementsPretest = null, bool? stateAndDiff = null)
|
||||
{
|
||||
var argsJson = CreateJsonArray(args);
|
||||
var methodObject = AddMethod(argsJson, implementsPretest, stateAndDiff);
|
||||
methodObject["input"] = "stdin";
|
||||
_manifest[method] = methodObject;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command method to the manifest.
|
||||
/// </summary>
|
||||
/// <param name="method">The name of the method to add.</param>
|
||||
/// <param name="args">The list of arguments for the method.</param>
|
||||
/// <returns>Returns the current instance of <see cref="DscManifest"/>.</returns>
|
||||
public DscManifest AddCommandMethod(string method, List<string> args)
|
||||
{
|
||||
_manifest[method] = new JsonObject
|
||||
{
|
||||
["command"] = AddMethod(CreateJsonArray(args)),
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the JSON representation of the manifest.
|
||||
/// </summary>
|
||||
/// <returns>Returns the JSON string of the manifest.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
return _manifest.ToJsonString(new() { WriteIndented = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a method to the manifest with the specified arguments.
|
||||
/// </summary>
|
||||
/// <param name="args">The list of arguments for the method.</param>
|
||||
/// <param name="implementsPretest">Whether the method implements a pretest.</param>
|
||||
/// <param name="stateAndDiff">Whether the method returns state and diff.</param>
|
||||
/// <returns>Returns the method object.</returns>
|
||||
private JsonObject AddMethod(JsonArray args, bool? implementsPretest = null, bool? stateAndDiff = null)
|
||||
{
|
||||
var methodObject = new JsonObject
|
||||
{
|
||||
["executable"] = Executable,
|
||||
["args"] = args,
|
||||
};
|
||||
|
||||
if (implementsPretest.HasValue)
|
||||
{
|
||||
methodObject["implementsPretest"] = implementsPretest.Value;
|
||||
}
|
||||
|
||||
if (stateAndDiff.HasValue)
|
||||
{
|
||||
methodObject["return"] = stateAndDiff.Value ? "stateAndDiff" : "state";
|
||||
}
|
||||
|
||||
return methodObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JSON array from a list of strings.
|
||||
/// </summary>
|
||||
/// <param name="args">The list of strings to convert.</param>
|
||||
/// <returns>Returns the JSON array.</returns>
|
||||
private JsonArray CreateJsonArray(List<string> args)
|
||||
{
|
||||
var jsonArray = new JsonArray();
|
||||
foreach (var arg in args)
|
||||
{
|
||||
jsonArray.Add(arg);
|
||||
}
|
||||
|
||||
return jsonArray;
|
||||
}
|
||||
}
|
||||
36
src/dsc/v3/PowerToys.DSC/Models/DscMessageLevel.cs
Normal file
36
src/dsc/v3/PowerToys.DSC/Models/DscMessageLevel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace PowerToys.DSC.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the severity level of a message.
|
||||
/// </summary>
|
||||
public enum DscMessageLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an error message.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a warning message.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Represents an informational message.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a debug message.
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a trace message.
|
||||
/// </summary>
|
||||
Trace,
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using NJsonSchema.Generation;
|
||||
using PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
namespace PowerToys.DSC.Models.FunctionData;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for function data objects.
|
||||
/// </summary>
|
||||
public class BaseFunctionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a JSON schema for the specified resource object type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the resource object.</typeparam>
|
||||
/// <returns>A JSON schema string.</returns>
|
||||
protected static string GenerateSchema<T>()
|
||||
where T : BaseResourceObject
|
||||
{
|
||||
var settings = new SystemTextJsonSchemaGeneratorSettings()
|
||||
{
|
||||
FlattenInheritanceHierarchy = true,
|
||||
SerializerOptions =
|
||||
{
|
||||
IgnoreReadOnlyFields = true,
|
||||
},
|
||||
};
|
||||
var generator = new JsonSchemaGenerator(settings);
|
||||
var schema = generator.Generate(typeof(T));
|
||||
return schema.ToJson(Formatting.None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Nodes;
|
||||
using PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
namespace PowerToys.DSC.Models.FunctionData;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for function data related to settings.
|
||||
/// </summary>
|
||||
public interface ISettingsFunctionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the input settings resource object.
|
||||
/// </summary>
|
||||
public ISettingsResourceObject Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output settings resource object.
|
||||
/// </summary>
|
||||
public ISettingsResourceObject Output { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current settings.
|
||||
/// </summary>
|
||||
public void GetState();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current settings.
|
||||
/// </summary>
|
||||
public void SetState();
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the current settings and the desired state are valid.
|
||||
/// </summary>
|
||||
/// <returns>True if the current settings match the desired state; otherwise false.</returns>
|
||||
public bool TestState();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the difference between the current settings and the desired state in JSON format.
|
||||
/// </summary>
|
||||
/// <returns>A JSON array representing the differences.</returns>
|
||||
public JsonArray GetDiffJson();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schema for the settings resource object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string Schema();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
namespace PowerToys.DSC.Models.FunctionData;
|
||||
|
||||
/// <summary>
|
||||
/// Represents function data for the settings DSC resource.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSettingsConfig">The module settings configuration type.</typeparam>
|
||||
public sealed class SettingsFunctionData<TSettingsConfig> : BaseFunctionData, ISettingsFunctionData
|
||||
where TSettingsConfig : ISettingsConfig, new()
|
||||
{
|
||||
private static readonly SettingsUtils _settingsUtils = new();
|
||||
private static readonly TSettingsConfig _settingsConfig = new();
|
||||
|
||||
private readonly SettingsResourceObject<TSettingsConfig> _input;
|
||||
private readonly SettingsResourceObject<TSettingsConfig> _output;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISettingsResourceObject Input => _input;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISettingsResourceObject Output => _output;
|
||||
|
||||
public SettingsFunctionData(string? input = null)
|
||||
{
|
||||
_output = new();
|
||||
_input = string.IsNullOrEmpty(input) ? new() : JsonSerializer.Deserialize<SettingsResourceObject<TSettingsConfig>>(input) ?? new();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GetState()
|
||||
{
|
||||
_output.Settings = GetSettings();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetState()
|
||||
{
|
||||
Debug.Assert(_output.Settings != null, "Output settings should not be null");
|
||||
SaveSettings(_output.Settings);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TestState()
|
||||
{
|
||||
var input = JsonSerializer.SerializeToNode(_input.Settings);
|
||||
var output = JsonSerializer.SerializeToNode(_output.Settings);
|
||||
return JsonNode.DeepEquals(input, output);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public JsonArray GetDiffJson()
|
||||
{
|
||||
var diff = new JsonArray();
|
||||
if (!TestState())
|
||||
{
|
||||
diff.Add(SettingsResourceObject<TSettingsConfig>.SettingsJsonPropertyName);
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Schema()
|
||||
{
|
||||
return GenerateSchema<SettingsResourceObject<TSettingsConfig>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings configuration from the settings utils for a specific module.
|
||||
/// </summary>
|
||||
/// <returns>The settings configuration for the module.</returns>
|
||||
private static TSettingsConfig GetSettings()
|
||||
{
|
||||
return _settingsUtils.GetSettingsOrDefault<TSettingsConfig>(_settingsConfig.GetModuleName());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the settings configuration to the settings utils for a specific module.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings of a specific module</param>
|
||||
private static void SaveSettings(TSettingsConfig settings)
|
||||
{
|
||||
var inputJson = JsonSerializer.Serialize(settings);
|
||||
_settingsUtils.SaveSettings(inputJson, _settingsConfig.GetModuleName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all resource objects.
|
||||
/// </summary>
|
||||
public class BaseResourceObject
|
||||
{
|
||||
private readonly JsonSerializerOptions _options;
|
||||
|
||||
public BaseResourceObject()
|
||||
{
|
||||
_options = new()
|
||||
{
|
||||
WriteIndented = false,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether an instance is in the desired state.
|
||||
/// </summary>
|
||||
[JsonPropertyName("_inDesiredState")]
|
||||
[Description("Indicates whether an instance is in the desired state")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public bool? InDesiredState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a JSON representation of the resource object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public JsonNode ToJson()
|
||||
{
|
||||
return JsonSerializer.SerializeToNode(this, GetType(), _options) ?? new JsonObject();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for settings resource objects.
|
||||
/// </summary>
|
||||
public interface ISettingsResourceObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the settings configuration.
|
||||
/// </summary>
|
||||
public ISettingsConfig SettingsInternal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether an instance is in the desired state.
|
||||
/// </summary>
|
||||
public bool? InDesiredState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a JSON representation of the resource object.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the resource object in JSON format.</returns>
|
||||
public JsonNode ToJson();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using NJsonSchema.Annotations;
|
||||
|
||||
namespace PowerToys.DSC.Models.ResourceObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a settings resource object for a module's settings configuration.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSettingsConfig">The type of the settings configuration.</typeparam>
|
||||
public sealed class SettingsResourceObject<TSettingsConfig> : BaseResourceObject, ISettingsResourceObject
|
||||
where TSettingsConfig : ISettingsConfig, new()
|
||||
{
|
||||
public const string SettingsJsonPropertyName = "settings";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the settings content for the module.
|
||||
/// </summary>
|
||||
[JsonPropertyName(SettingsJsonPropertyName)]
|
||||
[Required]
|
||||
[Description("The settings content for the module.")]
|
||||
[JsonSchemaType(typeof(object))]
|
||||
public TSettingsConfig Settings { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public ISettingsConfig SettingsInternal { get => Settings; set => Settings = (TSettingsConfig)value; }
|
||||
}
|
||||
51
src/dsc/v3/PowerToys.DSC/Options/InputOption.cs
Normal file
51
src/dsc/v3/PowerToys.DSC/Options/InputOption.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an option for specifying JSON input for the dsc command.
|
||||
/// </summary>
|
||||
public sealed class InputOption : Option<string>
|
||||
{
|
||||
private static readonly CompositeFormat InvalidJsonInputError = CompositeFormat.Parse(Resources.InvalidJsonInputError);
|
||||
|
||||
public InputOption()
|
||||
: base("--input", Resources.InputOptionDescription)
|
||||
{
|
||||
AddValidator(OptionValidator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the JSON input provided to the option.
|
||||
/// </summary>
|
||||
/// <param name="result">The option result to validate.</param>
|
||||
private void OptionValidator(OptionResult result)
|
||||
{
|
||||
var value = result.GetValueOrDefault<string>() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
result.ErrorMessage = Resources.InputEmptyOrNullError;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonDocument.Parse(value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.ErrorMessage = string.Format(CultureInfo.InvariantCulture, InvalidJsonInputError, e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/dsc/v3/PowerToys.DSC/Options/ModuleOption.cs
Normal file
19
src/dsc/v3/PowerToys.DSC/Options/ModuleOption.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an option for specifying the module name for the dsc command.
|
||||
/// </summary>
|
||||
public sealed class ModuleOption : Option<string?>
|
||||
{
|
||||
public ModuleOption()
|
||||
: base("--module", Resources.ModuleOptionDescription)
|
||||
{
|
||||
}
|
||||
}
|
||||
43
src/dsc/v3/PowerToys.DSC/Options/OutputDirectoryOption.cs
Normal file
43
src/dsc/v3/PowerToys.DSC/Options/OutputDirectoryOption.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an option for specifying the output directory for the dsc command.
|
||||
/// </summary>
|
||||
public sealed class OutputDirectoryOption : Option<string>
|
||||
{
|
||||
private static readonly CompositeFormat InvalidOutputDirectoryError = CompositeFormat.Parse(Resources.InvalidOutputDirectoryError);
|
||||
|
||||
public OutputDirectoryOption()
|
||||
: base("--outputDir", Resources.OutputDirectoryOptionDescription)
|
||||
{
|
||||
AddValidator(OptionValidator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the output directory option.
|
||||
/// </summary>
|
||||
/// <param name="result">The option result to validate.</param>
|
||||
private void OptionValidator(OptionResult result)
|
||||
{
|
||||
var value = result.GetValueOrDefault<string>() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
result.ErrorMessage = Resources.OutputDirectoryEmptyOrNullError;
|
||||
}
|
||||
else if (!Directory.Exists(value))
|
||||
{
|
||||
result.ErrorMessage = string.Format(CultureInfo.InvariantCulture, InvalidOutputDirectoryError, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/dsc/v3/PowerToys.DSC/Options/ResourceOption.cs
Normal file
43
src/dsc/v3/PowerToys.DSC/Options/ResourceOption.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using PowerToys.DSC.Properties;
|
||||
|
||||
namespace PowerToys.DSC.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an option for specifying the resource name for the dsc command.
|
||||
/// </summary>
|
||||
public sealed class ResourceOption : Option<string>
|
||||
{
|
||||
private static readonly CompositeFormat InvalidResourceNameError = CompositeFormat.Parse(Resources.InvalidResourceNameError);
|
||||
|
||||
private readonly IList<string> _resources = [];
|
||||
|
||||
public ResourceOption(IList<string> resources)
|
||||
: base("--resource", Resources.ResourceOptionDescription)
|
||||
{
|
||||
_resources = resources;
|
||||
IsRequired = true;
|
||||
AddValidator(OptionValidator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the resource option to ensure that the specified resource name is valid.
|
||||
/// </summary>
|
||||
/// <param name="result">The option result to validate.</param>
|
||||
private void OptionValidator(OptionResult result)
|
||||
{
|
||||
var value = result.GetValueOrDefault<string>() ?? string.Empty;
|
||||
if (!_resources.Contains(value))
|
||||
{
|
||||
result.ErrorMessage = string.Format(CultureInfo.InvariantCulture, InvalidResourceNameError, string.Join(", ", _resources));
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj
Normal file
48
src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj
Normal file
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.SelfContained.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<AssemblyName>PowerToys.DSC</AssemblyName>
|
||||
<AssemblyDescription>PowerToys DSC</AssemblyDescription>
|
||||
<RootNamespace>PowerToys.DSC</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Ensure WindowsDesktop runtime pack is included for consistent WindowsBase.dll version -->
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NJsonSchema" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- In debug mode, generate the DSC resource JSON files -->
|
||||
<Target Name="GenerateDscResourceJsonFiles" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
|
||||
<Message Text="Generating DSC resource JSON files inside ..." Importance="high" />
|
||||
<Exec Command="dotnet "$(TargetPath)" manifest --resource settings --outputDir "$(TargetDir)\"" />
|
||||
</Target>
|
||||
</Project>
|
||||
29
src/dsc/v3/PowerToys.DSC/Program.cs
Normal file
29
src/dsc/v3/PowerToys.DSC/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Threading.Tasks;
|
||||
using PowerToys.DSC.Commands;
|
||||
|
||||
namespace PowerToys.DSC;
|
||||
|
||||
/// <summary>
|
||||
/// Main entry point for the PowerToys Desired State Configuration CLI application.
|
||||
/// </summary>
|
||||
public class Program
|
||||
{
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
var rootCommand = new RootCommand(Properties.Resources.PowerToysDSC);
|
||||
rootCommand.AddCommand(new GetCommand());
|
||||
rootCommand.AddCommand(new SetCommand());
|
||||
rootCommand.AddCommand(new ExportCommand());
|
||||
rootCommand.AddCommand(new TestCommand());
|
||||
rootCommand.AddCommand(new SchemaCommand());
|
||||
rootCommand.AddCommand(new ManifestCommand());
|
||||
rootCommand.AddCommand(new ModulesCommand());
|
||||
return await rootCommand.InvokeAsync(args);
|
||||
}
|
||||
}
|
||||
234
src/dsc/v3/PowerToys.DSC/Properties/Resources.Designer.cs
generated
Normal file
234
src/dsc/v3/PowerToys.DSC/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,234 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PowerToys.DSC.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerToys.DSC.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Get all state instances.
|
||||
/// </summary>
|
||||
internal static string ExportCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to write manifests to directory '{0}': {1}.
|
||||
/// </summary>
|
||||
internal static string FailedToWriteManifests {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToWriteManifests", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Get the resource state.
|
||||
/// </summary>
|
||||
internal static string GetCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("GetCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Input cannot be empty or null.
|
||||
/// </summary>
|
||||
internal static string InputEmptyOrNullError {
|
||||
get {
|
||||
return ResourceManager.GetString("InputEmptyOrNullError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The JSON input.
|
||||
/// </summary>
|
||||
internal static string InputOptionDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("InputOptionDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid JSON input: {0}.
|
||||
/// </summary>
|
||||
internal static string InvalidJsonInputError {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidJsonInputError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid output directory: {0}.
|
||||
/// </summary>
|
||||
internal static string InvalidOutputDirectoryError {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidOutputDirectoryError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid resource name. Valid resource names are: {0}.
|
||||
/// </summary>
|
||||
internal static string InvalidResourceNameError {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidResourceNameError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Get the manifest of the dsc resource.
|
||||
/// </summary>
|
||||
internal static string ManifestCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ManifestCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Module '{0}' is not supported for the resource {1}. Use the 'module' command to list available modules..
|
||||
/// </summary>
|
||||
internal static string ModuleNotSupportedByResource {
|
||||
get {
|
||||
return ResourceManager.GetString("ModuleNotSupportedByResource", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The module name.
|
||||
/// </summary>
|
||||
internal static string ModuleOptionDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ModuleOptionDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Get all supported modules for a specific resource.
|
||||
/// </summary>
|
||||
internal static string ModulesCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ModulesCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Output directory cannot be empty or null.
|
||||
/// </summary>
|
||||
internal static string OutputDirectoryEmptyOrNullError {
|
||||
get {
|
||||
return ResourceManager.GetString("OutputDirectoryEmptyOrNullError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The output directory.
|
||||
/// </summary>
|
||||
internal static string OutputDirectoryOptionDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("OutputDirectoryOptionDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PowerToys Desired State Configuration commands.
|
||||
/// </summary>
|
||||
internal static string PowerToysDSC {
|
||||
get {
|
||||
return ResourceManager.GetString("PowerToysDSC", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The resource name.
|
||||
/// </summary>
|
||||
internal static string ResourceOptionDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ResourceOptionDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Outputs schema of the resource.
|
||||
/// </summary>
|
||||
internal static string SchemaCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("SchemaCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Set the resource state.
|
||||
/// </summary>
|
||||
internal static string SetCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("SetCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Test the resource state.
|
||||
/// </summary>
|
||||
internal static string TestCommandDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("TestCommandDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/dsc/v3/PowerToys.DSC/Properties/Resources.resx
Normal file
183
src/dsc/v3/PowerToys.DSC/Properties/Resources.resx
Normal file
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="PowerToysDSC" xml:space="preserve">
|
||||
<value>PowerToys Desired State Configuration commands</value>
|
||||
<comment>{Locked="PowerToys Desired State Configuration"}</comment>
|
||||
</data>
|
||||
<data name="ModuleNotSupportedByResource" xml:space="preserve">
|
||||
<value>Module '{0}' is not supported for the resource {1}. Use the 'module' command to list available modules.</value>
|
||||
<comment>{Locked="'module'","{0}","{1}"}</comment>
|
||||
</data>
|
||||
<data name="ExportCommandDescription" xml:space="preserve">
|
||||
<value>Get all state instances</value>
|
||||
</data>
|
||||
<data name="GetCommandDescription" xml:space="preserve">
|
||||
<value>Get the resource state</value>
|
||||
</data>
|
||||
<data name="ManifestCommandDescription" xml:space="preserve">
|
||||
<value>Get the manifest of the dsc resource</value>
|
||||
</data>
|
||||
<data name="ModulesCommandDescription" xml:space="preserve">
|
||||
<value>Get all supported modules for a specific resource</value>
|
||||
</data>
|
||||
<data name="SchemaCommandDescription" xml:space="preserve">
|
||||
<value>Outputs schema of the resource</value>
|
||||
</data>
|
||||
<data name="SetCommandDescription" xml:space="preserve">
|
||||
<value>Set the resource state</value>
|
||||
</data>
|
||||
<data name="TestCommandDescription" xml:space="preserve">
|
||||
<value>Test the resource state</value>
|
||||
</data>
|
||||
<data name="InputEmptyOrNullError" xml:space="preserve">
|
||||
<value>Input cannot be empty or null</value>
|
||||
</data>
|
||||
<data name="FailedToWriteManifests" xml:space="preserve">
|
||||
<value>Failed to write manifests to directory '{0}': {1}</value>
|
||||
<comment>{Locked="{0}","{1}"}</comment>
|
||||
</data>
|
||||
<data name="InputOptionDescription" xml:space="preserve">
|
||||
<value>The JSON input</value>
|
||||
</data>
|
||||
<data name="ModuleOptionDescription" xml:space="preserve">
|
||||
<value>The module name</value>
|
||||
</data>
|
||||
<data name="OutputDirectoryOptionDescription" xml:space="preserve">
|
||||
<value>The output directory</value>
|
||||
</data>
|
||||
<data name="ResourceOptionDescription" xml:space="preserve">
|
||||
<value>The resource name</value>
|
||||
</data>
|
||||
<data name="InvalidJsonInputError" xml:space="preserve">
|
||||
<value>Invalid JSON input: {0}</value>
|
||||
<comment>{Locked="{0}"}</comment>
|
||||
</data>
|
||||
<data name="OutputDirectoryEmptyOrNullError" xml:space="preserve">
|
||||
<value>Output directory cannot be empty or null</value>
|
||||
</data>
|
||||
<data name="InvalidOutputDirectoryError" xml:space="preserve">
|
||||
<value>Invalid output directory: {0}</value>
|
||||
<comment>{Locked="{0}"}</comment>
|
||||
</data>
|
||||
<data name="InvalidResourceNameError" xml:space="preserve">
|
||||
<value>Invalid resource name. Valid resource names are: {0}</value>
|
||||
<comment>{Locked="{0}"}</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -48,7 +48,6 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<None Include="KeyboardManager.base.rc" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -66,6 +65,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files\KeyboardManager.rc" />
|
||||
<None Include="KeyboardManager.base.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources.resx" />
|
||||
@@ -82,4 +82,7 @@
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h KeyboardManager.base.rc KeyboardManager.rc" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -124,6 +124,20 @@ else {
|
||||
Write-Warning "[SIGN] No .msix files found in $msixSearchRoot"
|
||||
}
|
||||
|
||||
# Generate DSC manifest files
|
||||
Write-Host '[DSC] Generating DSC manifest files...'
|
||||
$dscScriptPath = Join-Path $repoRoot '.\tools\build\generate-dsc-manifests.ps1'
|
||||
if (Test-Path $dscScriptPath) {
|
||||
& $dscScriptPath -BuildPlatform $Platform -BuildConfiguration $Configuration -RepoRoot $repoRoot
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "DSC manifest generation failed with exit code $LASTEXITCODE"
|
||||
exit 1
|
||||
}
|
||||
Write-Host '[DSC] DSC manifest files generated successfully'
|
||||
} else {
|
||||
Write-Warning "[DSC] DSC manifest generator script not found at: $dscScriptPath"
|
||||
}
|
||||
|
||||
RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' $commonArgs $Platform $Configuration
|
||||
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration
|
||||
|
||||
|
||||
116
tools/build/generate-dsc-manifests.ps1
Normal file
116
tools/build/generate-dsc-manifests.ps1
Normal file
@@ -0,0 +1,116 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BuildPlatform,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BuildConfiguration,
|
||||
|
||||
[Parameter()]
|
||||
[string]$RepoRoot = (Get-Location).Path,
|
||||
|
||||
[switch]$ForceRebuildExecutable
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Resolve-PlatformDirectory {
|
||||
param(
|
||||
[string]$Root,
|
||||
[string]$Platform
|
||||
)
|
||||
|
||||
$normalized = $Platform.Trim()
|
||||
$candidates = @()
|
||||
$candidates += Join-Path $Root $normalized
|
||||
$candidates += Join-Path $Root ($normalized.ToUpperInvariant())
|
||||
$candidates += Join-Path $Root ($normalized.ToLowerInvariant())
|
||||
$candidates = $candidates | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if (Test-Path $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
|
||||
return $candidates[0]
|
||||
}
|
||||
|
||||
Write-Host "Repo root: $RepoRoot"
|
||||
Write-Host "Requested build platform: $BuildPlatform"
|
||||
Write-Host "Requested configuration: $BuildConfiguration"
|
||||
|
||||
# Always use x64 PowerToys.DSC.exe since CI/CD machines are x64
|
||||
$exePlatform = 'x64'
|
||||
$exeRoot = Resolve-PlatformDirectory -Root $RepoRoot -Platform $exePlatform
|
||||
$exeOutputDir = Join-Path $exeRoot $BuildConfiguration
|
||||
$exePath = Join-Path $exeOutputDir 'PowerToys.DSC.exe'
|
||||
|
||||
Write-Host "Using x64 PowerToys.DSC.exe to generate DSC manifests for $BuildPlatform build"
|
||||
|
||||
if ($ForceRebuildExecutable -or -not (Test-Path $exePath)) {
|
||||
Write-Host "PowerToys.DSC.exe not found at '$exePath'. Building x64 binary..."
|
||||
|
||||
$msbuild = Get-Command msbuild.exe -ErrorAction SilentlyContinue
|
||||
if ($null -eq $msbuild) {
|
||||
throw "msbuild.exe was not found on the PATH."
|
||||
}
|
||||
|
||||
$projectPath = Join-Path $RepoRoot 'src\dsc\v3\PowerToys.DSC\PowerToys.DSC.csproj'
|
||||
$msbuildArgs = @(
|
||||
$projectPath,
|
||||
'/t:Build',
|
||||
'/m',
|
||||
"/p:Configuration=$BuildConfiguration",
|
||||
"/p:Platform=x64",
|
||||
'/restore'
|
||||
)
|
||||
|
||||
& $msbuild.Path @msbuildArgs
|
||||
$msbuildExitCode = $LASTEXITCODE
|
||||
|
||||
if ($msbuildExitCode -ne 0) {
|
||||
throw "msbuild build failed with exit code $msbuildExitCode"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $exePath)) {
|
||||
throw "Expected PowerToys.DSC.exe at '$exePath' after build but it was not found."
|
||||
}
|
||||
} else {
|
||||
Write-Host "Using existing PowerToys.DSC.exe at '$exePath'."
|
||||
}
|
||||
|
||||
# Output DSC manifests to the target build platform directory (x64, ARM64, etc.)
|
||||
$outputRoot = Resolve-PlatformDirectory -Root $RepoRoot -Platform $BuildPlatform
|
||||
if (-not (Test-Path $outputRoot)) {
|
||||
Write-Host "Creating missing platform output root at '$outputRoot'."
|
||||
New-Item -Path $outputRoot -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
$outputDir = Join-Path $outputRoot $BuildConfiguration
|
||||
if (-not (Test-Path $outputDir)) {
|
||||
Write-Host "Creating missing configuration output directory at '$outputDir'."
|
||||
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "DSC manifests will be generated to: '$outputDir'"
|
||||
|
||||
Write-Host "Cleaning previously generated DSC manifest files from '$outputDir'."
|
||||
Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
|
||||
|
||||
$arguments = @('manifest', '--resource', 'settings', '--outputDir', $outputDir)
|
||||
Write-Host "Invoking DSC manifest generator: '$exePath' $($arguments -join ' ')"
|
||||
& $exePath @arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "PowerToys.DSC.exe exited with code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$generatedFiles = Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
|
||||
if ($generatedFiles.Count -eq 0) {
|
||||
throw "No DSC manifest files were generated in '$outputDir'."
|
||||
}
|
||||
|
||||
Write-Host "Generated $($generatedFiles.Count) DSC manifest file(s):"
|
||||
foreach ($file in $generatedFiles) {
|
||||
Write-Host " - $($file.FullName)"
|
||||
}
|
||||
Reference in New Issue
Block a user