diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 86bb9fc1d4..37ce368394 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -94,6 +94,7 @@ ASSOCSTR ASYNCWINDOWPLACEMENT ASYNCWINDOWPOS atl +ATX ATRIOX aumid Authenticode @@ -113,6 +114,7 @@ azman bbwe BCIE bck +backticks BESTEFFORT bezelled bhid @@ -193,6 +195,7 @@ changecursor CHILDACTIVATE CHILDWINDOW CHOOSEFONT +CIBUILD cidl CIELCh cim @@ -267,6 +270,7 @@ countof covrun cpcontrols cph +cppcoreguidelines cplusplus CPower cpptools @@ -369,7 +373,7 @@ devmgmt DEVMODE DEVMODEW devpal -DFX +dfx DIALOGEX digicert DINORMAL @@ -383,6 +387,7 @@ DISPLAYFREQUENCY displayname DISPLAYORIENTATION divyan +djwsxzxb Dlg DLGFRAME DLGMODALFRAME @@ -443,6 +448,7 @@ EDITSHORTCUTS EDITTEXT EFile ekus +eku emojis ENABLEDELAYEDEXPANSION ENABLEDPOPUP @@ -507,6 +513,7 @@ eyetracker FANCYZONESDRAWLAYOUTTEST FANCYZONESEDITOR FARPROC +fdx fesf FFFF FILEEXPLORER @@ -635,6 +642,7 @@ Hiber Hiberboot HIBYTE hicon +HICONSM HIDEREADONLY HIDEWINDOW Hif @@ -815,6 +823,7 @@ keyvault KILLFOCUS killrunner kmph +ksa kvp Kybd LARGEICON @@ -921,6 +930,7 @@ LWA lwin LZero MAGTRANSFORM +makeappx MAKEINTRESOURCE MAKEINTRESOURCEA MAKEINTRESOURCEW @@ -1148,6 +1158,7 @@ ntfs NTSTATUS NTSYSAPI NULLCURSOR +nullref nullonfailure numberbox nwc @@ -1252,6 +1263,7 @@ pinvoke pipename PKBDLLHOOKSTRUCT pkgfamily +PKI plib ploc ploca @@ -1693,6 +1705,7 @@ syskeydown SYSKEYUP SYSLIB SYSMENU +systemai SYSTEMAPPS SYSTEMMODAL SYSTEMTIME @@ -1979,7 +1992,7 @@ WORKSPACESEDITOR WORKSPACESLAUNCHER WORKSPACESSNAPSHOTTOOL WORKSPACESWINDOWARRANGER -Worktree +worktree wox wparam wpf diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 092df9fc17..bb24140592 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,43 +1,59 @@ -# PowerToys – Copilot guide (concise) +--- +description: PowerToys AI contributor guidance. +applyTo: pullRequests +--- + +# PowerToys - Copilot guide (concise) This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs. -Repo map (1‑line per area) +# Repo map (1-line per area) - Core apps: `src/runner/**` (tray/loader), `src/settings-ui/**` (Settings app) - Shared libs: `src/common/**` - Modules: `src/modules/*` (one per utility; Command Palette in `src/modules/cmdpal/**`) - Build tools/docs: `tools/**`, `doc/devdocs/**` -Build and test (defaults) +# Build and test (defaults) - Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+. - Build discipline: - - One terminal per operation (build → test). Don’t switch/open new ones mid-flow. + - One terminal per operation (build -> test). Do not switch or open new ones mid-flow. - After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`). - - Use script(s) to build, synchronously block and wait in foreground for it to finish: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages) - - Treat build **exit code 0** as success; any non-zero exit code is a failure, have Copilot read the errors log in the build folder (e.g., `build.*.*.errors.log`) and surface problems. - - Don’t start tests or launch Runner until the previous step succeeded. -- Tests (fast + targeted): - - Find the test project by product code prefix (e.g., FancyZones, AdvancedPaste). Look for a sibling folder or 1–2 levels up named like `*UnitTests` or `*UITests`. - - Build the test project, wait for **exit**, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo. - - Add/adjust tests when changing behavior; if skipped, state why (e.g., comment-only, string rename). + - Use scripts to build, synchronously block and wait in foreground for completion: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages). + - Treat build exit code 0 as success; any non-zero exit code is a failure. Read the errors log in the build folder (such as `build.*.*.errors.log`) and surface problems. + - Do not start tests or launch Runner until the previous step succeeded. +- Tests (fast and targeted): + - Find the test project by product code prefix (for example FancyZones, AdvancedPaste). Look for a sibling folder or one to two levels up named like `*UnitTests` or `*UITests`. + - Build the test project, wait for exit, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo. + - Add or adjust tests when changing behavior; if skipped, state why (for example comment-only or string rename). -Pull requests (expectations) -- Atomic: one logical change; no drive‑by refactors. -- Describe: problem / approach / risk / test evidence. +# Pull requests (expectations) +- Atomic: one logical change; no drive-by refactors. +- Describe: problem, approach, risk, test evidence. - List: touched paths if not obvious. -When to ask for clarification +# When to ask for clarification - Ambiguous spec after scanning relevant docs (see below). -- Cross-module impact (shared enum/struct) not clear. -- Security / elevation / installer changes. +- Cross-module impact (shared enum or struct) not clear. +- Security, elevation, or installer changes. -Logging (use existing stacks) -- C++: `src/common/logger/**` (`Logger::info|warn|error|debug`). Keep hot paths quiet (hooks, tight loops). -- C#: `ManagedCommon.Logger` (`LogInfo|LogWarning|LogError|LogDebug|LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`. +# Logging (use existing stacks) +- C++ logging lives in `src/common/logger/**` (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`). Keep hot paths quiet (hooks, tight loops). +- C# logging goes through `ManagedCommon.Logger` (`LogInfo`, `LogWarning`, `LogError`, `LogDebug`, `LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`. -Docs to consult +# Docs to consult - `tools/build/BUILD-GUIDELINES.md` -- `doc/devdocs/core/architecture.md`, `doc/devdocs/core/runner.md`, `doc/devdocs/core/settings/readme.md`, `doc/devdocs/modules/readme.md` +- `doc/devdocs/core/architecture.md` +- `doc/devdocs/core/runner.md` +- `doc/devdocs/core/settings/readme.md` +- `doc/devdocs/modules/readme.md` -Done checklist (self review before finishing) -- Build clean? Tests updated/passed? No unintended formatting? Any new dependency? Documented skips? \ No newline at end of file +# Language style rules +- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`. +- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers. +- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability). +- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags. +- YAML files indent two spaces and add comments for complex settings while keeping keys clear. +- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer. + +# Done checklist (self review before finishing) +- Build clean? Tests updated or passed? No unintended formatting? Any new dependency? Documented skips? diff --git a/.github/prompts/create-commit-title.prompt.md b/.github/prompts/create-commit-title.prompt.md new file mode 100644 index 0000000000..5d84aef163 --- /dev/null +++ b/.github/prompts/create-commit-title.prompt.md @@ -0,0 +1,16 @@ +--- +mode: 'agent' +model: GPT-5-Codex (Preview) +description: 'Generate an 80-character git commit title for the local diff.' +--- + +**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`. + +**Workflow:** +1. Run a single command to view the local diff since the last commit: + ```@terminal + git diff HEAD + ``` +2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact. +3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`). +4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`. diff --git a/.github/prompts/create-pr-summary.prompt.md b/.github/prompts/create-pr-summary.prompt.md new file mode 100644 index 0000000000..0c324e7578 --- /dev/null +++ b/.github/prompts/create-pr-summary.prompt.md @@ -0,0 +1,22 @@ +--- +mode: 'agent' +model: GPT-5-Codex (Preview) +description: 'Generate a PowerToys-ready pull request description from the local diff.' +--- + +**Goal:** Produce a ready-to-paste PR title and description that follows PowerToys conventions by comparing the current branch against a user-selected target branch. + +**Repo guardrails:** +- Treat `.github/pull_request_template.md` as the single source of truth; load it at runtime instead of embedding hardcoded content in this prompt. +- Preserve section order from the template but only surface checklist lines that are relevant for the detected changes, filling them with `[x]`/`[ ]` as appropriate. +- Cite touched paths with inline backticks, matching the guidance in `.github/copilot-instructions.md`. +- Call out test coverage explicitly: list automated tests run (unit/UI) or state why they are not applicable. + +**Workflow:** +1. Determine the target branch from user context; default to `main` when no branch is supplied. +2. Run `git status --short` once to surface uncommitted files that may influence the summary. +3. Run `git diff ...HEAD` a single time to review the detailed changes. Only when confidence stays low dig deeper with focused calls such as `git diff ...HEAD -- `. +4. From the diff, capture impacted areas, key file changes, behavioral risks, migrations, and noteworthy edge cases. +5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance. +6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A". +7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input. diff --git a/.github/prompts/fix-spelling.prompt.md b/.github/prompts/fix-spelling.prompt.md new file mode 100644 index 0000000000..e0007ff724 --- /dev/null +++ b/.github/prompts/fix-spelling.prompt.md @@ -0,0 +1,22 @@ +--- +mode: 'agent' +model: GPT-5-Codex (Preview) +description: 'Resolve Code scanning / check-spelling comments on the active PR.' +--- + +**Goal:** Clear every outstanding GitHub pull request comment created by the `Code scanning / check-spelling` workflow by explicitly allowing intentional terms. + +**Guardrails:** +- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`. +- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries. +- Leave all other files and topics untouched. + +**Prerequisites:** +- Install GitHub CLI if it is not present: `winget install GitHub.cli`. +- Run `gh auth login` once before the first CLI use. + +**Workflow:** +1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch). +2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body. +3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists. +4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact. \ No newline at end of file diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 2a1760d94e..d99fabf0eb 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -235,7 +235,9 @@ "*Microsoft.CmdPal.UI_*.msix", "PowerToys.DSC.dll", - "PowerToys.DSC.exe" + "PowerToys.DSC.exe", + + "PowerToysSparse.msix" ], "SigningInfo": { "Operations": [ diff --git a/.pipelines/ESRPSigning_installer.json b/.pipelines/ESRPSigning_installer.json index cd96fb6f64..c9e505d3a2 100644 --- a/.pipelines/ESRPSigning_installer.json +++ b/.pipelines/ESRPSigning_installer.json @@ -4,7 +4,6 @@ "SignBatches": [ { "MatchedPath": [ - "PowerToysSetupCustomActions.dll", "PowerToysSetupCustomActionsVNext.dll", "SilentFilesInUseBAFunction.dll", "PowerToys*Setup-*.exe", diff --git a/.pipelines/installWiX.ps1 b/.pipelines/installWiX.ps1 deleted file mode 100644 index 3b6d783c85..0000000000 --- a/.pipelines/installWiX.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' - -$WixDownloadUrl = "https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe" -$WixBinariesDownloadUrl = "https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip" - -# Download WiX binaries and verify their hash sums -Invoke-WebRequest -Uri $WixDownloadUrl -OutFile "$($ENV:Temp)\wix314.exe" -$Hash = (Get-FileHash -Algorithm SHA256 "$($ENV:Temp)\wix314.exe").Hash -if ($Hash -ne '6BF6D03D6923D9EF827AE1D943B90B42B8EBB1B0F68EF6D55F868FA34C738A29') -{ - Write-Error "$WixHash" - throw "wix314.exe has unexpected SHA256 hash: $Hash" -} -Invoke-WebRequest -Uri $WixBinariesDownloadUrl -OutFile "$($ENV:Temp)\wix314-binaries.zip" -$Hash = (Get-FileHash -Algorithm SHA256 "$($ENV:Temp)\wix314-binaries.zip").Hash -if($Hash -ne '6AC824E1642D6F7277D0ED7EA09411A508F6116BA6FAE0AA5F2C7DAA2FF43D31') -{ - throw "wix314-binaries.zip has unexpected SHA256 hash: $Hash" -} - -# Install WiX -Start-Process -Wait -FilePath "$($ENV:Temp)\wix314.exe" -ArgumentList "/install /quiet" - -# Extract WiX binaries and copy wix.targets to the installed dir -Expand-Archive -Path "$($ENV:Temp)\wix314-binaries.zip" -Force -DestinationPath "$($ENV:Temp)" -Copy-Item -Path "$($ENV:Temp)\wix.targets" -Destination "C:\Program Files (x86)\WiX Toolset v3.14\" \ No newline at end of file diff --git a/.pipelines/v2/release.yml b/.pipelines/v2/release.yml index 9db2993a28..e8cc7d5ed8 100644 --- a/.pipelines/v2/release.yml +++ b/.pipelines/v2/release.yml @@ -20,11 +20,6 @@ parameters: type: string default: '0.0.1' - - name: installerSuffix - type: string - displayName: "WiX5 installer suffix (e.g., 'wix5', 'vnext', etc.)" - default: "wix5" - - name: buildConfigurations displayName: "Build Configurations" type: object @@ -105,8 +100,7 @@ extends: useManagedIdentity: $(SigningUseManagedIdentity) clientId: $(SigningOriginalClientId) # Have msbuild use the release nuget config profile - installerSuffix: ${{ parameters.installerSuffix }} - additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:InstallerSuffix=${{ parameters.installerSuffix }} /p:EnableCmdPalAOT=true + additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=true beforeBuildSteps: # Sets versions for all PowerToy created DLLs - pwsh: |- diff --git a/.pipelines/v2/templates/job-build-project.yml b/.pipelines/v2/templates/job-build-project.yml index 57c478f643..2ffd0c7f62 100644 --- a/.pipelines/v2/templates/job-build-project.yml +++ b/.pipelines/v2/templates/job-build-project.yml @@ -65,9 +65,6 @@ parameters: - name: versionNumber type: string default: '0.0.1' - - name: installerSuffix - type: string - default: "wix5" - name: useLatestWinAppSDK type: boolean default: false @@ -240,9 +237,7 @@ jobs: parameters: directory: $(build.sourcesdirectory)\src\modules\cmdpal - - pwsh: |- - & "$(build.sourcesdirectory)\.pipelines\installWiX.ps1" - displayName: Download and install WiX 3.14 development build + - ${{ parameters.beforeBuildSteps }} @@ -508,20 +503,7 @@ jobs: Copy-Item -Verbose -Force "$(CmdPalPackagePath)" "$(JobOutputDirectory)" displayName: Stage the final CmdPal package - - template: steps-build-installer.yml - parameters: - codeSign: ${{ parameters.codeSign }} - signingIdentity: ${{ parameters.signingIdentity }} - versionNumber: ${{ parameters.versionNumber }} - additionalBuildOptions: ${{ parameters.additionalBuildOptions }} - - template: steps-build-installer.yml - parameters: - codeSign: ${{ parameters.codeSign }} - signingIdentity: ${{ parameters.signingIdentity }} - versionNumber: ${{ parameters.versionNumber }} - additionalBuildOptions: ${{ parameters.additionalBuildOptions }} - buildUserInstaller: true # NOTE: This is the distinction between the above and below rules - template: steps-build-installer-vnext.yml parameters: @@ -529,7 +511,6 @@ jobs: signingIdentity: ${{ parameters.signingIdentity }} versionNumber: ${{ parameters.versionNumber }} additionalBuildOptions: ${{ parameters.additionalBuildOptions }} - installerSuffix: ${{ parameters.installerSuffix }} - template: steps-build-installer-vnext.yml parameters: @@ -537,7 +518,6 @@ jobs: signingIdentity: ${{ parameters.signingIdentity }} versionNumber: ${{ parameters.versionNumber }} additionalBuildOptions: ${{ parameters.additionalBuildOptions }} - installerSuffix: ${{ parameters.installerSuffix }} buildUserInstaller: true # NOTE: This is the distinction between the above and below rules # This saves ~1GiB per architecture. We won't need these later. @@ -577,17 +557,16 @@ jobs: - pwsh: |- $p = "$(JobOutputDirectory)\" - $installerSuffix = "${{ parameters.installerSuffix }}" - # Calculate hashes for regular installers (without custom suffix) - $userSetupFiles = Get-ChildItem -Path $p -Filter "PowerToysUserSetup*.exe" | Where-Object { $_.Name -notmatch "-$installerSuffix-" } - $machineSetupFiles = Get-ChildItem -Path $p -Filter "PowerToysSetup*.exe" | Where-Object { $_.Name -notmatch "-$installerSuffix-" -and $_.Name -notmatch "PowerToysUserSetup" } + # Calculate hashes for installers + $userSetupFiles = Get-ChildItem -Path $p -Filter "PowerToysUserSetup*.exe" + $machineSetupFiles = Get-ChildItem -Path $p -Filter "PowerToysSetup*.exe" | Where-Object { $_.Name -notmatch "PowerToysUserSetup" } if ($userSetupFiles.Count -gt 0) { $userHash = ($userSetupFiles[0] | Get-FileHash).Hash; $userPlat = "hash_user_$(BuildPlatform).txt"; $combinedUserPath = $p + $userPlat; - echo "Regular User: $userHash" + echo "User: $userHash" $userHash | out-file -filepath $combinedUserPath } @@ -595,29 +574,9 @@ jobs: $machineHash = ($machineSetupFiles[0] | Get-FileHash).Hash; $machinePlat = "hash_machine_$(BuildPlatform).txt"; $combinedMachinePath = $p + $machinePlat; - echo "Regular Machine: $machineHash" + echo "Machine: $machineHash" $machineHash | out-file -filepath $combinedMachinePath } - - # Calculate hashes for VNext installers (with custom suffix) - $userVNextFiles = Get-ChildItem -Path $p -Filter "PowerToysUserSetup*-$installerSuffix-*.exe" - $machineVNextFiles = Get-ChildItem -Path $p -Filter "PowerToysSetup*-$installerSuffix-*.exe" | Where-Object { $_.Name -notmatch "PowerToysUserSetup" } - - if ($userVNextFiles.Count -gt 0) { - $userVNextHash = ($userVNextFiles[0] | Get-FileHash).Hash; - $userVNextPlat = "hash_user_vnext_$(BuildPlatform).txt"; - $combinedUserVNextPath = $p + $userVNextPlat; - echo "VNext User: $userVNextHash" - $userVNextHash | out-file -filepath $combinedUserVNextPath - } - - if ($machineVNextFiles.Count -gt 0) { - $machineVNextHash = ($machineVNextFiles[0] | Get-FileHash).Hash; - $machineVNextPlat = "hash_machine_vnext_$(BuildPlatform).txt"; - $combinedMachineVNextPath = $p + $machineVNextPlat; - echo "VNext Machine: $machineVNextHash" - $machineVNextHash | out-file -filepath $combinedMachineVNextPath - } displayName: Calculate file hashes for all installers # Publishing the GPO files diff --git a/.pipelines/v2/templates/steps-build-installer-vnext.yml b/.pipelines/v2/templates/steps-build-installer-vnext.yml index 6db52ec631..882df8696a 100644 --- a/.pipelines/v2/templates/steps-build-installer-vnext.yml +++ b/.pipelines/v2/templates/steps-build-installer-vnext.yml @@ -14,9 +14,6 @@ parameters: - name: additionalBuildOptions type: string default: '' - - name: installerSuffix - type: string - default: "wix5" steps: # Install WiX 5.0.2 tools needed for VNext installer (matching project SDK) @@ -41,10 +38,10 @@ steps: # VNext bundle folder; base name intentionally omits the VNext suffix $InstallerFolder = 'PowerToysSetupVNext' if ($IsPerUser) { - $InstallerBasename = "PowerToysUserSetup-${{ parameters.versionNumber }}-${{ parameters.installerSuffix }}-$(BuildPlatform)" + $InstallerBasename = "PowerToysUserSetup-${{ parameters.versionNumber }}-$(BuildPlatform)" } else { - $InstallerBasename = "PowerToysSetup-${{ parameters.versionNumber }}-${{ parameters.installerSuffix }}-$(BuildPlatform)" + $InstallerBasename = "PowerToysSetup-${{ parameters.versionNumber }}-$(BuildPlatform)" } # Export variables for downstream steps @@ -63,7 +60,6 @@ steps: msbuildArgs: >- /t:PowerToysSetupCustomActionsVNext /p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true - /p:InstallerSuffix=${{ parameters.installerSuffix }} -restore -graph /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-actions.binlog ${{ parameters.additionalBuildOptions }} @@ -95,7 +91,6 @@ steps: -restore /t:PowerToysInstallerVNext /p:RunBuildEvents=false;PerUser=${{parameters.buildUserInstaller}};BuildProjectReferences=false;CIBuild=true - /p:InstallerSuffix=${{ parameters.installerSuffix }} /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-msi.binlog ${{ parameters.additionalBuildOptions }} platform: $(BuildPlatform) @@ -142,7 +137,6 @@ steps: msbuildArgs: >- /t:SilentFilesInUseBAFunction /p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true - /p:InstallerSuffix=${{ parameters.installerSuffix }} -restore -graph /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-SilentFilesInUseBAFunction.binlog ${{ parameters.additionalBuildOptions }} @@ -175,7 +169,6 @@ steps: -restore /t:PowerToysBootstrapperVNext /p:PerUser=${{parameters.buildUserInstaller}};CIBuild=true - /p:InstallerSuffix=${{ parameters.installerSuffix }} /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-bootstrapper.binlog -restore -graph ${{ parameters.additionalBuildOptions }} diff --git a/.pipelines/v2/templates/steps-build-installer.yml b/.pipelines/v2/templates/steps-build-installer.yml deleted file mode 100644 index 8c3c89dbc0..0000000000 --- a/.pipelines/v2/templates/steps-build-installer.yml +++ /dev/null @@ -1,208 +0,0 @@ -parameters: - - name: versionNumber - type: string - default: "0.0.1" - - name: buildUserInstaller - type: boolean - default: false - - name: codeSign - type: boolean - default: false - - name: signingIdentity - type: object - default: {} - - name: additionalBuildOptions - type: string - default: '' - -steps: - - pwsh: |- - & git clean -xfd -e *exe -- .\installer\ - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Clean installer to reduce cross-contamination - - - pwsh: |- - $IsPerUser = $${{ parameters.buildUserInstaller }} - $InstallerBuildSlug = "MachineSetup" - $InstallerBasename = "PowerToysSetup" - If($IsPerUser) { - $InstallerBuildSlug = "UserSetup" - $InstallerBasename = "PowerToysUserSetup" - } - $InstallerBasename += "-${{ parameters.versionNumber }}-$(BuildPlatform)" - Write-Host "##vso[task.setvariable variable=InstallerBuildSlug]$InstallerBuildSlug" - Write-Host "##vso[task.setvariable variable=InstallerRelativePath]$(BuildPlatform)\$(BuildConfiguration)\$InstallerBuildSlug" - Write-Host "##vso[task.setvariable variable=InstallerBasename]$InstallerBasename" - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Prepare Installer variables - - # This dll needs to be built and signed before building the MSI. - - task: VSBuild@1 - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build PowerToysSetupCustomActions - inputs: - solution: "**/installer/PowerToysSetup.sln" - vsVersion: 17.0 - msbuildArgs: >- - /t:PowerToysSetupCustomActions - /p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true - -restore -graph - /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-actions.binlog - ${{ parameters.additionalBuildOptions }} - platform: $(BuildPlatform) - configuration: $(BuildConfiguration) - clean: true - msbuildArchitecture: x64 - maximumCpuCount: true - - - ${{ if eq(parameters.codeSign, true) }}: - - template: steps-esrp-signing.yml - parameters: - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign PowerToysSetupCustomActions - signingIdentity: ${{ parameters.signingIdentity }} - inputs: - FolderPath: 'installer/PowerToysSetupCustomActions/$(InstallerRelativePath)' - signType: batchSigning - batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json' - ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml' - - ## INSTALLER START - #### MSI BUILDING AND SIGNING - - task: VSBuild@1 - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build MSI - inputs: - solution: "**/installer/PowerToysSetup.sln" - vsVersion: 17.0 - msbuildArgs: >- - -restore - /t:PowerToysInstaller - /p:RunBuildEvents=false;PerUser=${{parameters.buildUserInstaller}};BuildProjectReferences=false;CIBuild=true - /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-msi.binlog - ${{ parameters.additionalBuildOptions }} - platform: $(BuildPlatform) - configuration: $(BuildConfiguration) - clean: false # don't undo our hard work above by deleting the CustomActions dll - msbuildArchitecture: x64 - maximumCpuCount: true - - - script: |- - "C:\Program Files (x86)\WiX Toolset v3.14\bin\dark.exe" -x $(build.sourcesdirectory)\extractedMsi installer\PowerToysSetup\$(InstallerRelativePath)\$(InstallerBasename).msi - dir $(build.sourcesdirectory)\extractedMsi - displayName: "${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Extract and verify MSI" - - # Extract CmdPal msix package to check if its content is signed - - pwsh: |- - Write-Host "Extracting CmdPal MSIX package" - - # Define the directory to search - $searchDir = "extractedMsi\File" - - # Define the regex pattern for MSIX files - $pattern = '^Microsoft.CmdPal.UI.*\.msix$' - - # Get all files in the directory and subdirectories - $msixFile = Get-ChildItem -Path $searchDir -Recurse -File | Where-Object { - $_.Name -match $pattern - } - - Write-Host "MSIX file found: " $msixFile - - $destinationDir = "$(build.sourcesdirectory)\extractedMsi\File\extractedCmdPalMsix" - - Expand-Archive -Path $msixFile -DestinationPath $destinationDir - Get-ChildItem -Path $destinationDir -Recurse -File - - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Extract CmdPal MSIX package - - # Check if deps.json files don't reference different dll versions. - - pwsh: |- - & '.pipelines/verifyDepsJsonLibraryVersions.ps1' -targetDir '$(build.sourcesdirectory)\extractedMsi\File' - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Audit deps.json in MSI extracted files - - - ${{ if eq(parameters.codeSign, true) }}: - - pwsh: |- - & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedMsi\File' - & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedMsi\Binary' - git clean -xfd ./extractedMsi - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Verify all binaries are signed and versioned - - - template: steps-esrp-signing.yml - parameters: - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign MSI - signingIdentity: ${{ parameters.signingIdentity }} - inputs: - FolderPath: 'installer/PowerToysSetup/$(InstallerRelativePath)' - signType: batchSigning - batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json' - ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml' - - #### END MSI - #### BOOTSTRAP BUILDING AND SIGNING - - - task: VSBuild@1 - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build Bootstrapper - inputs: - solution: "**/installer/PowerToysSetup.sln" - vsVersion: 17.0 - msbuildArgs: >- - /t:PowerToysBootstrapper - /p:PerUser=${{parameters.buildUserInstaller}};CIBuild=true - /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-bootstrapper.binlog - -restore -graph - ${{ parameters.additionalBuildOptions }} - platform: $(BuildPlatform) - configuration: $(BuildConfiguration) - clean: false # don't undo our hard work above by deleting the MSI - msbuildArchitecture: x64 - maximumCpuCount: true - - # The entirety of bundle unpacking/re-packing is unnecessary if we are not code signing it. - - ${{ if eq(parameters.codeSign, true) }}: - - script: |- - "C:\Program Files (x86)\WiX Toolset v3.14\bin\insignia.exe" -ib installer\PowerToysSetup\$(InstallerRelativePath)\$(InstallerBasename).exe -o installer\engine.exe - displayName: "${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Insignia: Extract Engine from Bundle" - - - template: steps-esrp-signing.yml - parameters: - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign WiX Engine - signingIdentity: ${{ parameters.signingIdentity }} - inputs: - FolderPath: "installer" - Pattern: engine.exe - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "KeyCode": "CP-230012", - "OperationCode": "SigntoolSign", - "Parameters": { - "OpusName": "Microsoft", - "OpusInfo": "http://www.microsoft.com", - "FileDigest": "/fd \"SHA256\"", - "PageHash": "/NPH", - "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" - }, - "ToolName": "sign", - "ToolVersion": "1.0" - }, - { - "KeyCode": "CP-230012", - "OperationCode": "SigntoolVerify", - "Parameters": {}, - "ToolName": "sign", - "ToolVersion": "1.0" - } - ] - - - script: |- - "C:\Program Files (x86)\WiX Toolset v3.14\bin\insignia.exe" -ab installer\engine.exe installer\PowerToysSetup\$(InstallerRelativePath)\$(InstallerBasename).exe -o installer\PowerToysSetup\$(InstallerRelativePath)\$(InstallerBasename).exe - displayName: "${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Insignia: Merge Engine into Bundle" - - - template: steps-esrp-signing.yml - parameters: - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign Final Bootstrapper - signingIdentity: ${{ parameters.signingIdentity }} - inputs: - FolderPath: 'installer/PowerToysSetup/$(InstallerRelativePath)' - signType: batchSigning - batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json' - ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml' - #### END BOOTSTRAP - ## END INSTALLER diff --git a/Directory.Packages.props b/Directory.Packages.props index 3fe8abe8fb..129abadb00 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -61,6 +61,8 @@ + + diff --git a/PowerToys.sln b/PowerToys.sln index f8f26cde91..f4f2e1bd0f 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -26,6 +26,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} {D940E07F-532C-4FF3-883F-790DA014F19A} = {D940E07F-532C-4FF3-883F-790DA014F19A} {DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A} + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D} = {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D} {E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B} {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} EndProjectSection @@ -50,6 +51,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{1AFB64 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Lib.UnitTests", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PackageIdentity", "src\PackageIdentity\PackageIdentity.vcxproj", "{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}" @@ -867,6 +870,14 @@ Global {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|ARM64.Build.0 = Release|ARM64 {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64 {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.Build.0 = Debug|ARM64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.Build.0 = Debug|x64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.ActiveCfg = Release|ARM64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.Build.0 = Release|ARM64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.ActiveCfg = Release|x64 + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.Build.0 = Release|x64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.ActiveCfg = Debug|ARM64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.Build.0 = Debug|ARM64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/README.md b/README.md index 631b43d6aa..85fad26e1f 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@

Installation - · + . Documentation - · + . Blog - · + . Release notes



@@ -27,11 +27,12 @@ Microsoft PowerToys is a collection of utilities that help you customize Windows | [Color Picker icon Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Command Not Found icon Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Command Palette icon Command Palette](https://aka.ms/PowerToysOverview_CmdPal) | | [Crop and Lock icon Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables icon Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones icon FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | | [File Explorer Add-ons icon File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith icon File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor icon Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | -| [Image Resizer icon Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager icon Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse Utilities icon Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | -| [Mouse Without Borders icon Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+ icon New+](https://aka.ms/PowerToysOverview_NewPlus) | [Peek icon Peek](https://aka.ms/PowerToysOverview_Peek) | -| [PowerRename icon PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run icon PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent icon Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | -| [Registry Preview icon Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler icon Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide icon Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | -| [Text Extractor icon Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Workspaces icon Workspaces](https://aka.ms/PowerToysOverview_Workspaces) | [ZoomIt icon ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) | +| [Image Resizer icon Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager icon Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Light Switch icon Light Switch](https://aka.ms/PowerToysOverview_LightSwitch) | +| [Mouse Utilities icon Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders icon Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+ icon New+](https://aka.ms/PowerToysOverview_NewPlus) | +| [Peek icon Peek](https://aka.ms/PowerToysOverview_Peek) | [PowerRename icon PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run icon PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | +| [Quick Accent icon Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview icon Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler icon Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | +| [Shortcut Guide icon Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor icon Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Workspaces icon Workspaces](https://aka.ms/PowerToysOverview_Workspaces) | +| [ZoomIt icon ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) | | | ## 📋 Installation @@ -53,19 +54,19 @@ Choose one of the installation methods below: Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer. -[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22 -[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.94%22 -[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysUserSetup-0.94.0-x64.exe -[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysUserSetup-0.94.0-arm64.exe -[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysSetup-0.94.0-x64.exe -[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysSetup-0.94.0-arm64.exe +[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22 +[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22 +[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysUserSetup-0.95.0-x64.exe +[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysUserSetup-0.95.0-arm64.exe +[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysSetup-0.95.0-x64.exe +[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysSetup-0.95.0-arm64.exe | Description | Filename | |----------------|----------| -| Per user - x64 | [PowerToysUserSetup-0.94.0-x64.exe][ptUserX64] | -| Per user - ARM64 | [PowerToysUserSetup-0.94.0-arm64.exe][ptUserArm64] | -| Machine wide - x64 | [PowerToysSetup-0.94.0-x64.exe][ptMachineX64] | -| Machine wide - ARM64 | [PowerToysSetup-0.94.0-arm64.exe][ptMachineArm64] | +| Per user - x64 | [PowerToysUserSetup-0.95.0-x64.exe][ptUserX64] | +| Per user - ARM64 | [PowerToysUserSetup-0.95.0-arm64.exe][ptUserArm64] | +| Machine wide - x64 | [PowerToysSetup-0.95.0-x64.exe][ptMachineX64] | +| Machine wide - ARM64 | [PowerToysSetup-0.95.0-arm64.exe][ptMachineArm64] | @@ -105,175 +106,179 @@ There are [community driven install methods](./doc/unofficialInstallMethods.md) ## ✨ What's new -**Version 0.94 (September 2025)** +**Version 0.95 (October 2025)** For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog). **✨ Highlights** - - - PowerToys Settings added a Settings search with fuzzy matching, suggestions, a results page, and UX polish to make finding options faster. - - A comprehensive hotkey conflict detection system was introduced in Settings to surface and help resolve conflicting shortcuts. Note that the default hotkey settings (Win+Ctrl+Shift+T, Win+Ctrl+V, Win+Ctrl+T, Win+Shift+T) may overlap with existing Windows system shortcuts. This is expected. You can resolve the conflict by assigning different hotkeys. - - Mouse Utilities added a “Gliding cursor” accessibility feature to Mouse Pointer Crosshairs for single‑button cursor movement and clicking. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! - - The installer was upgraded to WiX 5 after WiX 3 reached end-of-life; this move improved installer security, reliability, and community support. - - Tons of bug fixes and improvements for Command Palette, including visual updates and new support for filters on ListPages (handy for extension developers). - - Hosts Editor now has a “No leading spaces” option so active host entries can start at column 0 even if others are disabled. Thanks [@mohammed-saalim](https://github.com/mohammed-saalim)! - - Context menu registration was moved from the installer to runtime to avoid loading disabled modules (runtime registrations). - - Quick Accent now supports Maltese, and frequently used accents appear first (and are remembered across sessions). Thanks [@rovercoder](https://github.com/rovercoder)! [@davidegiacometti](https://github.com/davidegiacometti)! - -### Always On Top - - - Fixed the border hover cursor so it shows the arrow instead of the wait cursor. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - **NEW:** The **Light Switch** utility in PowerToys allows you to automatically switch between light and dark themes in Windows based on the time of day. + - Command Palette delivered major search performance gains (new fuzzy matcher and smarter fallbacks) improving relevance and speed. + - Peek can now be activated using just the Spacebar! + - Find My Mouse added transparent spotlight with independent backdrop opacity, boosting focus and accessibility. + - Settings now lets you delete shortcuts entirely and ignore conflicts. + - Mouse Pointer Crosshairs gained orientation options (vertical / horizontal / both) for customizable accessibility. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! + - PowerRename fixed enumeration counter skipping ensuring reliable batch renames. Thanks [@daverayment](https://github.com/daverayment)! + - ZoomIt restored legacy draw and snipping behaviors, and fixed recording issues, improving reliability. Thanks [@chakrik73](https://github.com/chakrik73)! ### Command Palette + - Applied conditional margin for icon-only tags to tighten layout. Thanks [@samrueby](https://github.com/samrueby) + - Improved the reliability of accessing Command Palette settings through PowerToys Settings and executing other x-cmdpal:// protocol commands. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Enabled AOT by default for improved performance while simplifying publish configs. + - Replaced service state color dots with play/pause/stop icons for enhanced accessibility. Thanks [@samrueby](https://github.com/samrueby) + - Fixed filter dropdown sync and crash by binding SelectedValue and raising UI-thread notifications. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Ensured long links wrap correctly in details view. + - Removed animation and enforced minimum width on filter dropdown for clarity. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Restored focus to More button after ESC closes context menu, improving keyboard flow. Thanks [@chatasweetie](https://github.com/chatasweetie) + - Marked main and toast windows as tool windows to keep them out of Alt+Tab while preserving style. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Fixed AOT template and theming issues for filter separators. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Introduced grid layouts (small, medium, gallery) for richer page presentation. + - Materialized result lists to avoid rescoring overhead. + - Disabled problematic selection TextToSuggest behind environment flag. + - Major search performance improvements (new fuzzy matcher, smarter fallbacks, fewer exceptions). + - Added context menu "Show Details" command when details pane is hidden. + - Reduced window flicker by avoiding unnecessary cloaking. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Restored EmptyContent rendering for blank states. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme) + - Saved new state even if prior app state file was corrupt (better resilience). Thanks [@jiripolasek](https://github.com/jiripolasek) + - Migrated settings window to WinUI TitleBar control. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Prevented crash on duplicate keybindings and simplified matching. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Hotkeys now always respect the “Ignore shortcut in fullscreen” setting. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Hid search box on content pages, improving focus and accessibility, and added Home title. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Blocked Ctrl+I from inserting stray tabs in search box. + - Logged HRESULT codes in error logs for deeper diagnostics. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Advanced font and emoji icon classification and alignment improvements. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Ensured that fallback command icons are visible on the extension settings page. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Fixed breadcrumb margin misalignment (visual polish). Thanks [@jiripolasek](https://github.com/jiripolasek) + - Truncated overly long command labels with ellipsis to prevent overflow. + - Added a setting to configure the page transition animation. + - Collection of small improvements and nits for Run Commands. + - Improved bookmarks performance and experience. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Added Ctrl+O shortcut in Clipboard History to open links directly. + - Resolved conflict with external software that blocked Command Palette from hiding. + - Updated context menu items to reflect name and icon changes, and ensured application icons are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Added Alt+Home shortcut to return immediately to the Command Palette home page. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Fixed a crash when displaying code blocks in markdown on detail or content pages. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Fixed an issue where the search bar icon and title were not updated when rapidly switching pages. Thanks [@jiripolasek](https://github.com/jiripolasek) + - Improved the appearance of the search box in the context menu. - - Applied single-click activation only to pointer input; keyboard always activates immediately. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Let context menus open at the cursor by removing window-bound constraints. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Made error messages clearer with timestamps, HRESULTs, and full details for easier diagnosis. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Prevented crashes and improved robustness when updating providers without commands. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Ensured the Settings window reliably comes to the front when opened. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Replaced the Clipboard History icon with a colorful Fluent icon. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Hardened ContentIcon to avoid duplicate parenting and improve diagnostics. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Standardized null checks using C# pattern matching for safer behavior. - - Improved accessibility by focusing the activation shortcut dialog and making text reachable. Thanks [@chatasweetie](https://github.com/chatasweetie)! - - Moved the extension SDK to a stable Windows SDK and cleaned up message namespaces. - - Added path shortcuts: ~ to home, and / or \\ to system root, plus UNC support. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Fixed a race in cancellation handling to avoid InvalidOperationException. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Aligned separator styling with WinUI 3 for consistent visuals. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added ARM64 PDBs to the Extensions SDK NuGet for better debugging. - - Added single-select filters to DynamicListPage and updated Windows Services sample. - - Updated main page placeholder text to better describe what can be searched. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Removed explicit WinAppSDK/WebView2 dependencies from toolkit and API. Thanks [@rluengen](https://github.com/rluengen)! - - Added a local keyboard hook to handle the GoBack key reliably. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Propagated alias changes safely and resolved conflicts across view models. - - Allowed providers to override Dispose with a virtual method. - - Fixed memory leaks by cleaning up removed or cancelled list items. - - Sorted DateTime extension results by relevance for better usability. - - Reduced search text "jiggling" by avoiding redundant change notifications. - - Centralized automation notifications in a UIHelper for better accessibility. Thanks [@chatasweetie](https://github.com/chatasweetie)! - - Preserved Adaptive Card action types during trimming via DynamicDependency. - - Added an acrylic backdrop and refined styling to the context menu. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Prevented disposed pages and Settings windows from handling stale messages. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Made the extension API easier to evolve without breaking clients. - - Added "evil" sample pages to help reproduce tricky bugs. - - Fixed WinGet trim-safety issues by replacing LINQ with manual iteration. - - Cancelled stale list fetches to avoid older results overwriting newer ones in CmdPal. -### Command Palette extensions +### Command Palette Extensions + - Replaced localized WebSearch setting keys with stable literals and numeric history count. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Enabled advanced markdown tables and emphasis extensions. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added setting to choose Clipboard History primary action (Paste vs Copy). Thanks [@jiripolasek](https://github.com/jiripolasek) + - Added actionable empty-state hints for File Search (search PC / open indexing settings). Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Ensured all WinGet extension assets copy reliably to output. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Improved Run command line parsing for paths with spaces; sped up related tests. + - Updated WebSearch extension icon set for enhanced clarity and contrast. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added Terminal profile sort order setting including MRU tracking. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added Uninstall Application command (UWP direct, Win32 via Settings). Thanks [@mKpwnz](https://github.com/mKpwnz)! + - Deferred WinGet details loading and added timing logs. + - Removed LINQ from All Apps extension for performance. + - Added standardized key chord system + shortcuts to File Search commands. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added Terminal channel filter & remembered selection option. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Enabled loading local/data/app images in markdown with sizing hints. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added external extension reload via x-cmdpal://reload (configurable). Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Instant WebSearch history updates with in-memory store & events. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added keep-after-paste option and safe delete with confirmation for Clipboard History. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Improved empty states and ranking logic for multiple extensions. Thanks [@htcfreek](https://github.com/htcfreek)! - - Added app icons to the All Apps "Run" context command when available. - - Restored missing builtin icons by standardizing extension dependencies. - - Unblocked local deployment by adding WinAppSDK to two sample extensions. +### Environment Variables + - Replaced custom window chrome with WinUI TitleBar for cleaner, maintainable Environment Variables UI. + +### File Locksmith + - Adopted WinUI TitleBar to simplify window chrome while preserving appearance. + +### Find My Mouse + - Added transparent spotlight support with separate backdrop opacity; migrated to Windows App SDK composition APIs. ### Hosts File Editor + - Migrated to native WinUI TitleBar for cleaner, maintainable window chrome. - - Added a "No leading spaces" option so active hosts entries can start at column 0 even when others are disabled. Thanks [@mohammed-saalim](https://github.com/mohammed-saalim)! +### Light Switch + - Introduced as a brand-new PowerToy module. + - Automatically switches between light and dark themes. + - Supports time-based scheduling or location-based sunrise/sunset switching. + - Supports using a keyboard shortcut to force a change. + - Supports filtering changes for Apps and/or System Theme. -### Image Resizer - - - Fixed Image Resizer localization by installing satellite resources under the WinUI 3 apps culture path. - -### Mouse Utilities - - - Introduced "Gliding cursor" to control the pointer and click with a single hotkey for better accessibility. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! +### Mouse Pointer Crosshairs + - Added Esc key to cancel active gliding cursor sequence. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! + - Added orientation option (vertical / horizontal / both) for crosshairs customization. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! ### Mouse Without Borders + - Continued Common class refactor (part 5/7) by extracting clipboard and init/cleanup logic into focused classes. Thanks [@mikeclayton](https://github.com/mikeclayton)! - - Blocked Easy Mouse from switching machines during fullscreen apps, with an allow-list for exceptions. Thanks [@dot-tb](https://github.com/dot-tb)! + - Fix connection failures caused by conflicting MachineId across machines. Thanks [@noraa-junker](https://github.com/noraa-junker) for troubleshooting! ### Peek - - - Added Visual Studio shared project file types to XML preview and fixed bgcode handler registration. Thanks [@rezanid](https://github.com/rezanid)! - - Fixes bgcode preview handler registration and events for reliable previews. Thanks [@pedrolamas](https://github.com/pedrolamas)! + - Added the option to activate Peek with just the Spacebar. ### PowerRename - - - Changed the Explorer accelerator key to PowErRename to avoid clashing with the New menu. Thanks [@aaron-ni](https://github.com/aaron-ni)! + - Fixed enumeration counter skipping when regex replacement equals original filename (counters now advance reliably). Thanks [@daverayment](https://github.com/daverayment)! ### Quick Accent + - Expanded Welsh layout with acute, grave, and dieresis variants for vowels (consistent ordering). Thanks [@PesBandi](https://github.com/PesBandi)! - - Remembered character usage across sessions so frequently used accents appear first. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Added Maltese language support with specific characters and the Euro symbol. Thanks [@rovercoder](https://github.com/rovercoder)! - - Reduced GPU usage issues by making the window Topmost only when the picker is visible. Thanks [@daverayment](https://github.com/daverayment)! +### Registry Preview + - Migrated to native TitleBar and AppWindow APIs for cleaner window chrome. + +### Screen Ruler + - Fixed ARM64 crash by aligning cursor position structure to 8-byte boundary. ### Settings + - Added ability to ignore specific hotkey conflicts to reduce noise. + - Stopped creating backup directory during dry-run status checks (cleaner first-run). + - Standardized casing and localization for ZoomIt and modules header. + - Improved search results page accessibility and conditional module grouping. - - Added telemetry to track usage of the new shortcut conflict detection workflow. - - Moved the shutdown action from the title bar to a footer menu item with confirmation. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Implemented comprehensive hotkey conflict detection with a dedicated resolution dialog. - - Added branded visuals for Office and Copilot keys in the KeyVisual control. - - Introduced Settings search with fuzzy matching and navigation to specific controls. - - Corrected Spanish localization so product names like Awake remain in English across Settings and OOBE. - - Simplified the Advanced Paste description in Settings for quicker reading and consistent capitalization. Thanks [@OldUser101](https://github.com/OldUser101)! - - Localized conflict messages in the conflict window and dialog. +### ZoomIt + - Updated resource file to reflect standalone v9.01 and current copyright year. Thanks [@foxmsft](https://github.com/foxmsft)! + - Restored legacy draw/snipping behaviors and fixed recording race conditions. Thanks [@chakrik73](https://github.com/chakrik73)! + - Added smooth image option for improved zoom quality using GDI+ for static zoom and Magnifier API for live zoom. Thanks [@markrussinovich](https://github.com/markrussinovich)! -### Installer + ### Documentation + - New Microsoft Learn documentation for the Light Switch module. + - New dev docs for the Light Switch module. - - Upgraded the installer to WiX 5 with silent "Files in Use" handling for smoother winget installs. - - Switched Win10 context menu modules to runtime registration and added cleanup on uninstall to avoid stale entries. +### Development (Area-Build & Area-Tests) +- Allowed debug launches to continue when modules fail to load, speeding developer iteration. +- Fixed spell checker dictionary entry (advapi) to eliminate false error. +- Added VS Code development guide and launch configs to streamline cross-editor workflows. +- Upgraded Windows App SDK and related dependencies to 1.8 for newer platform features. +- Rewrote YAML comment to resolve new spell checker forbidden pattern. Thanks [@jiripolasek](https://github.com/jiripolasek)! +- Corrected solution structure by returning misplaced Common project, reducing build confusion. +- Modernized build scripts with shared helpers and VS environment autodetection for simpler CLI builds. +- Standardized build scripts and platform detection to improve reliability and reuse. +- Added missing Command Palette version bump to align module release cadence. +- Added EXECUTEDEFAULT term to dictionary to prevent regression build failures. Thanks [@jiripolasek](https://github.com/jiripolasek)! +- Introduced nightly pre-warm pipeline and configurable MSBuild cache mode to improve CI performance. +- Resolved CI forbidden pattern spelling complaint to keep pipelines green. +- Added AI contributor instruction set to clarify code area expectations. +- Added accessibility IDs to settings and FancyZones toggles, stabilizing UI tests. +- Added automatic log collection on UI test failures to speed root cause analysis. +- Stabilized Mouse Utils tests by switching to AccessibilityId selectors. +- Added Screen Ruler UI test coverage to validate core measurement workflows. -### Documentation - - - Adds docs for building the installer locally and testing winget installs. - - Fixed a broken style guide link in developer documentation. Thanks [@denizmaral](https://github.com/denizmaral)! - -### Development - - - Excluded test and coverage DLLs from BinSkim scans to cut false positives and speed up security analysis. - - Simplified NOTICE maintenance by removing version numbers and filtering out Microsoft/System packages. - - Improved NuGet dependency validation to prevent package downgrades and catch issues during restore. - - Updated UTF.Unknown to a modern version to improve compatibility without breaking changes. Thanks [@304NotModified](https://github.com/304NotModified)! - - Refreshed package catalog in CI before installing dependencies to prevent Linux workflow failures. - - Refactored CmdPal tests with dependency injection and added coverage for queries and settings. - - Added unit tests to verify Close on Enter swaps Copy/Save as expected. Thanks [@mohammed-saalim](https://github.com/mohammed-saalim)! - - Added accessibility IDs to CmdPal UI for stable UI tests. - - Rewrote system command tests with a new test base and cleaner patterns. - - Added unit tests for WebSearch and Shell extensions with mockable settings. - - Added unit tests and abstractions for Apps and Bookmarks extensions. - - Cleans up AI-generated tests; adds meaningful query tests across extensions. - - Removed the obsolete debug dialog from Settings for a smoother developer loop. - -## 🛣️ Roadmap - -For [v0.95][github-next-release-work], we'll work on the items below: - - - Continued Command Palette polish - - Working on Shortcut Guide v2 (Thanks [@noraa-junker](https://github.com/noraa-junker)!) - - Upgrading Keyboard Manager's editor UI - - UI tweaking utility with day/night theme switcher - - DSC v3 support for top utilities - - New UI automation tests - - Stability, bug fixes - -## ❤️ PowerToys Community +## 🛣️ Roadmap +We are planning some nice new features and improvements for the next releases – a revamped Keyboard Manager UI, custom endpoint and local model support for Advanced Paste, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.96][github-next-release-work]! +## ❤️ PowerToys Community The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldn't be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Your contributions and feedback improve PowerToys month after month! -## Contributing +## Contributing +This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows. We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](CONTRIBUTING.md). We would be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort. Most contributions require you to agree to a [Contributor License Agreement (CLA)][oss-CLA] declaring that you grant us the rights to use your contribution and that you have permission to do so. For guidance on developing for PowerToys, please read the [developer docs](./doc/devdocs) for a detailed breakdown. This includes how to setup your computer to compile. -This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows. +## Code of Conduct +This project has adopted the [Microsoft Open Source Code of Conduct][oss-conduct-code]. -We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](CONTRIBUTING.md). We would be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort. +## Privacy Statement +The application logs basic diagnostic data (telemetry). For more privacy information and what we collect, see our [PowerToys Data and Privacy documentation](https://aka.ms/powertoys-data-and-privacy-documentation). -Most contributions require you to agree to a [Contributor License Agreement (CLA)][oss-CLA] declaring that you grant us the rights to use your contribution and that you have permission to do so. - -For guidance on developing for PowerToys, please read the [developer docs](./doc/devdocs) for a detailed breakdown. This includes how to setup your computer to compile. - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct][oss-conduct-code]. - -## Privacy Statement - -The application logs basic diagnostic data (telemetry). For more privacy information and what we collect, see our [PowerToys Data and Privacy documentation](https://aka.ms/powertoys-data-and-privacy-documentation). - -[oss-CLA]: https://cla.opensource.microsoft.com -[oss-conduct-code]: CODE_OF_CONDUCT.md -[community-link]: COMMUNITY.md -[github-release-link]: https://aka.ms/installPowerToys -[microsoft-store-link]: https://aka.ms/getPowertoys -[winget-link]: https://github.com/microsoft/winget-cli#installing-the-client +[oss-CLA]: https://cla.opensource.microsoft.com +[oss-conduct-code]: CODE_OF_CONDUCT.md +[community-link]: COMMUNITY.md +[github-release-link]: https://aka.ms/installPowerToys +[microsoft-store-link]: https://aka.ms/getPowertoys +[winget-link]: https://github.com/microsoft/winget-cli#installing-the-client [roadmap]: https://github.com/microsoft/PowerToys/wiki/Roadmap -[privacy-link]: http://go.microsoft.com/fwlink/?LinkId=521839 -[loc-bug]: https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=translation_issue.md&title= -[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs +[privacy-link]: http://go.microsoft.com/fwlink/?LinkId=521839 +[loc-bug]: https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=translation_issue.md&title= +[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs \ No newline at end of file diff --git a/doc/devdocs/core/installer.md b/doc/devdocs/core/installer.md index 8e9f008f2e..5bcbb0f87c 100644 --- a/doc/devdocs/core/installer.md +++ b/doc/devdocs/core/installer.md @@ -1,6 +1,6 @@ # PowerToys Installer -## Installer Architecture (WiX 3/ WiX 5) +## Installer Architecture (WiX 5) - Uses a bootstrapper to check dependencies and close PowerToys - MSI defined in product.wxs @@ -22,7 +22,7 @@ ### MSI Installer Build Process -- First builds `PowerToysSetupCustomActions` DLL and signs it, for WiX5 project, installer will build `PowerToysSetupCustomActionsVNext` DLL. +- First builds `PowerToysSetupCustomActionsVNext` DLL and signs it - Then builds the installer without cleaning, to reuse the signed DLL - Uses PowerShell scripts to modify .wxs files before build - Restores original .wxs files after build completes @@ -96,9 +96,14 @@ The following manual steps will not install the MSIX apps (such as Command Palet #### Prerequisites for building the MSI installer -1. Install the [WiX Toolset Visual Studio 2022 Extension](https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2022Extension). -1. Install the [WiX Toolset build tools](https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm). (installer [direct link](https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe)) -1. Download [WiX binaries](https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip) and extract `wix.targets` to `C:\Program Files (x86)\WiX Toolset v3.14`. +PowerToys uses WiX v5 for creating installers. The WiX v5 tools are automatically installed during the build process via dotnet tool. + +For manual installation of WiX v5 tools: +```powershell +dotnet tool install --global wix --version 5.0.2 +``` + +> **Note:** As of release 0.94, PowerToys has migrated from WiX v3 to WiX v5. The WiX v3 toolset is no longer required. #### Building prerequisite projects @@ -133,17 +138,9 @@ If you prefer, you can alternatively build prerequisite projects for the install 1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release` 1. From the `Build` menu choose `Build Solution`. -The resulting `PowerToysSetup.msi` installer will be available in the `installer\PowerToysSetup\x64\Release\` folder. +The resulting installer will be available in the `installer\PowerToysSetupVNext\x64\Release\` folder. -For WiX3 project, run `Developer Command Prompt for VS 2022` in admin mode and execute the following command to build the installer. The generated installer package will be located at `\installer\PowerToysSetup\{platform}\Release\MachineSetup`. - -``` -git clean -xfd -e *exe -- .\installer\ -MSBuild -t:restore .\installer\PowerToysSetup.sln -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release -MSBuild -m .\installer\PowerToysSetup.sln /t:PowerToysInstaller /p:Configuration=Release /p:Platform="x64" -MSBuild -m .\installer\PowerToysSetup.sln /t:PowerToysBootstrapper /p:Configuration=Release /p:Platform="x64" -``` -For WiX5 project, run `Developer Command Prompt for VS 2022` in admin mode and execute the following command to build the installer. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`. +To build the installer from the command line, run `Developer Command Prompt for VS 2022` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`. ``` git clean -xfd -e *exe -- .\installer\ diff --git a/installer/PowerToysSetup.sln b/installer/PowerToysSetup.sln index 47d0c56070..77d38c94ab 100644 --- a/installer/PowerToysSetup.sln +++ b/installer/PowerToysSetup.sln @@ -2,16 +2,10 @@ # Visual Studio Version 17 VisualStudioVersion = 17.1.32414.318 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysInstaller", "PowerToysSetup\PowerToysInstaller.wixproj", "{022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActions", "PowerToysSetupCustomActions\PowerToysSetupCustomActions.vcxproj", "{32F3882B-F2D6-4586-B5ED-11E39E522BD3}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "..\src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}" EndProject -Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysBootstrapper", "PowerToysSetup\PowerToysBootstrapper.wixproj", "{31D72625-43C1-41B1-B784-BCE4A8DC5543}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "..\src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "..\src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}" @@ -32,21 +26,6 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Debug|ARM64.Build.0 = Debug|ARM64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Debug|x64.ActiveCfg = Debug|x64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Debug|x64.Build.0 = Debug|x64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Release|ARM64.ActiveCfg = Release|ARM64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Release|ARM64.Build.0 = Release|ARM64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Release|x64.ActiveCfg = Release|x64 - {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Release|x64.Build.0 = Release|x64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Debug|x64.ActiveCfg = Debug|x64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Debug|x64.Build.0 = Debug|x64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|ARM64.ActiveCfg = Release|ARM64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|ARM64.Build.0 = Release|ARM64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|x64.ActiveCfg = Release|x64 - {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|x64.Build.0 = Release|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.ActiveCfg = Debug|ARM64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64 @@ -61,14 +40,6 @@ Global {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.Build.0 = Release|ARM64 {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64 {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Debug|ARM64.Build.0 = Debug|ARM64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Debug|x64.ActiveCfg = Debug|x64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Debug|x64.Build.0 = Debug|x64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Release|ARM64.ActiveCfg = Release|ARM64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Release|ARM64.Build.0 = Release|ARM64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Release|x64.ActiveCfg = Release|x64 - {31D72625-43C1-41B1-B784-BCE4A8DC5543}.Release|x64.Build.0 = Release|x64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.Build.0 = Debug|ARM64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/installer/PowerToysSetup/AdvancedPaste.wxs b/installer/PowerToysSetup/AdvancedPaste.wxs deleted file mode 100644 index a865ddbf6c..0000000000 --- a/installer/PowerToysSetup/AdvancedPaste.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Awake.wxs b/installer/PowerToysSetup/Awake.wxs deleted file mode 100644 index a8f5536ff4..0000000000 --- a/installer/PowerToysSetup/Awake.wxs +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/BaseApplications.wxs b/installer/PowerToysSetup/BaseApplications.wxs deleted file mode 100644 index 134a3ee89a..0000000000 --- a/installer/PowerToysSetup/BaseApplications.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/CmdPal.wxs b/installer/PowerToysSetup/CmdPal.wxs deleted file mode 100644 index cce523e8b4..0000000000 --- a/installer/PowerToysSetup/CmdPal.wxs +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/ColorPicker.wxs b/installer/PowerToysSetup/ColorPicker.wxs deleted file mode 100644 index 0c744a7b26..0000000000 --- a/installer/PowerToysSetup/ColorPicker.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi deleted file mode 100644 index 21855a7936..0000000000 --- a/installer/PowerToysSetup/Common.wxi +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Core.wxs b/installer/PowerToysSetup/Core.wxs deleted file mode 100644 index eb39fdc9db..0000000000 --- a/installer/PowerToysSetup/Core.wxs +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSTALLDESKTOPSHORTCUT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/CustomDialogs/PTInstallDirDlg.wxs b/installer/PowerToysSetup/CustomDialogs/PTInstallDirDlg.wxs deleted file mode 100644 index d5697ec631..0000000000 --- a/installer/PowerToysSetup/CustomDialogs/PTInstallDirDlg.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/CustomDialogs/PTLicenseDlg.wxs b/installer/PowerToysSetup/CustomDialogs/PTLicenseDlg.wxs deleted file mode 100644 index ee7b752591..0000000000 --- a/installer/PowerToysSetup/CustomDialogs/PTLicenseDlg.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - 1 - - - - !(wix.WixUICostingPopupOptOut) OR CostingComplete = 1 - - - 1 - - - - - - - - \ No newline at end of file diff --git a/installer/PowerToysSetup/CustomDialogs/WixUI_PTInstallDir.wxs b/installer/PowerToysSetup/CustomDialogs/WixUI_PTInstallDir.wxs deleted file mode 100644 index a06d1ed278..0000000000 --- a/installer/PowerToysSetup/CustomDialogs/WixUI_PTInstallDir.wxs +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - "1"]]> - - 1 - - NOT Installed - Installed AND PATCH - - 1 - 1 - - 1 - 1 - NOT WIXUI_DONTVALIDATEPATH - "1"]]> - WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" - 1 - 1 - NOT Installed - Installed AND NOT PATCH - Installed AND PATCH - - 1 - - 1 - 1 - 1 - - - - - diff --git a/installer/PowerToysSetup/EnvironmentVariables.wxs b/installer/PowerToysSetup/EnvironmentVariables.wxs deleted file mode 100644 index 44567055af..0000000000 --- a/installer/PowerToysSetup/EnvironmentVariables.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/FileExplorerPreview.wxs b/installer/PowerToysSetup/FileExplorerPreview.wxs deleted file mode 100644 index 0a92d94c3e..0000000000 --- a/installer/PowerToysSetup/FileExplorerPreview.wxs +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/FileLocksmith.wxs b/installer/PowerToysSetup/FileLocksmith.wxs deleted file mode 100644 index 5943ab4147..0000000000 --- a/installer/PowerToysSetup/FileLocksmith.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Hosts.wxs b/installer/PowerToysSetup/Hosts.wxs deleted file mode 100644 index cb86aa8e11..0000000000 --- a/installer/PowerToysSetup/Hosts.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/ImageResizer.wxs b/installer/PowerToysSetup/ImageResizer.wxs deleted file mode 100644 index 67b5acf198..0000000000 --- a/installer/PowerToysSetup/ImageResizer.wxs +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Images/banner.png b/installer/PowerToysSetup/Images/banner.png deleted file mode 100644 index 25f878ee82..0000000000 Binary files a/installer/PowerToysSetup/Images/banner.png and /dev/null differ diff --git a/installer/PowerToysSetup/Images/dialog.png b/installer/PowerToysSetup/Images/dialog.png deleted file mode 100644 index b422990982..0000000000 Binary files a/installer/PowerToysSetup/Images/dialog.png and /dev/null differ diff --git a/installer/PowerToysSetup/Images/logo.png b/installer/PowerToysSetup/Images/logo.png deleted file mode 100644 index c9f9405405..0000000000 Binary files a/installer/PowerToysSetup/Images/logo.png and /dev/null differ diff --git a/installer/PowerToysSetup/Images/logo150.png b/installer/PowerToysSetup/Images/logo150.png deleted file mode 100644 index 35e757d8e5..0000000000 Binary files a/installer/PowerToysSetup/Images/logo150.png and /dev/null differ diff --git a/installer/PowerToysSetup/Images/logo44.png b/installer/PowerToysSetup/Images/logo44.png deleted file mode 100644 index b931931dff..0000000000 Binary files a/installer/PowerToysSetup/Images/logo44.png and /dev/null differ diff --git a/installer/PowerToysSetup/KeyboardManager.wxs b/installer/PowerToysSetup/KeyboardManager.wxs deleted file mode 100644 index dc216ccde3..0000000000 --- a/installer/PowerToysSetup/KeyboardManager.wxs +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/MouseWithoutBorders.wxs b/installer/PowerToysSetup/MouseWithoutBorders.wxs deleted file mode 100644 index 8a5efa1f8d..0000000000 --- a/installer/PowerToysSetup/MouseWithoutBorders.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/NewPlus.wxs b/installer/PowerToysSetup/NewPlus.wxs deleted file mode 100644 index 624c01fca2..0000000000 --- a/installer/PowerToysSetup/NewPlus.wxs +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Peek.wxs b/installer/PowerToysSetup/Peek.wxs deleted file mode 100644 index f87794e945..0000000000 --- a/installer/PowerToysSetup/Peek.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/PowerRename.wxs b/installer/PowerToysSetup/PowerRename.wxs deleted file mode 100644 index 7aa357e207..0000000000 --- a/installer/PowerToysSetup/PowerRename.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/PowerToys.wxs b/installer/PowerToysSetup/PowerToys.wxs deleted file mode 100644 index 2e50d278fb..0000000000 --- a/installer/PowerToysSetup/PowerToys.wxs +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MinimumVersion >= DetectedPowerToysVersion - TargetPowerToysVersion >= DetectedPowerToysUserVersion OR WixBundleInstalled - - MinimumVersion >= DetectedPowerToysUserVersion - TargetPowerToysVersion >= DetectedPowerToysVersion OR WixBundleInstalled - - - - - DetectedWindowsBuildNumber >= 19041 OR WixBundleInstalled - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/PowerToysBootstrapper.wixproj b/installer/PowerToysSetup/PowerToysBootstrapper.wixproj deleted file mode 100644 index b2f5945dc2..0000000000 --- a/installer/PowerToysSetup/PowerToysBootstrapper.wixproj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Version=$(Version) - PowerToysBootstrapper - {31d72625-43c1-41b1-b784-bce4a8dc5543} - - - $(DefineConstants);PerUser=true - - - $(DefineConstants);PerUser=false - - - $(DefineConstants);CIBuild=true - - - $(DefineConstants);CIBuild=false - - - Release - x64 - arm64 - 3.10 - 2.0 - PowerToysSetup-$(Version)-$(Platform) - Bundle - True - PowerToysSetup-$(Version)-$(Platform) - PowerToysUserSetup-$(Version)-$(Platform) - $(Platform)\$(Configuration)\MachineSetup - $(Platform)\$(Configuration)\UserSetup - obj\$(Platform)\$(Configuration)\ - - - - - - - - $(WixExtDir)\WixUtilExtension.dll - WixUtilExtension - - - $(WixExtDir)\WixUIExtension.dll - WixUIExtension - - - $(WixExtDir)\WixNetFxExtension.dll - WixNetFxExtension - - - $(WixExtDir)\WixBalExtension.dll - WixBalExtension - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - <_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" /> - - - - \ No newline at end of file diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj deleted file mode 100644 index 4a391eb901..0000000000 --- a/installer/PowerToysSetup/PowerToysInstaller.wixproj +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion) - - IF NOT DEFINED IsPipeline ( -call "$([MSBuild]::GetVsInstallRoot())\Common7\Tools\VsDevCmd.bat" -arch=amd64 -host_arch=amd64 -winsdk=10.0.19041.0 -vcvars_ver=$(VCToolsVersion) -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)" - - - - Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion); - IF NOT DEFINED IsPipeline ( -call "$([MSBuild]::GetVsInstallRoot())\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=amd64 -winsdk=10.0.19041.0 -vcvars_ver=$(VCToolsVersion) -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)" - - - - Always - - call move /Y ..\..\..\AdvancedPaste.wxs.bk ..\..\..\AdvancedPaste.wxs - call move /Y ..\..\..\Awake.wxs.bk ..\..\..\Awake.wxs - call move /Y ..\..\..\BaseApplications.wxs.bk ..\..\..\BaseApplications.wxs - call move /Y ..\..\..\CmdPal.wxs.bk ..\..\..\CmdPal.wxs - call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs - call move /Y ..\..\..\Core.wxs.bk ..\..\..\Core.wxs - call move /Y ..\..\..\EnvironmentVariables.wxs.bk ..\..\..\EnvironmentVariables.wxs - call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs - call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs - call move /Y ..\..\..\Hosts.wxs.bk ..\..\..\Hosts.wxs - call move /Y ..\..\..\ImageResizer.wxs.bk ..\..\..\ImageResizer.wxs - call move /Y ..\..\..\KeyboardManager.wxs.bk ..\..\..\KeyboardManager.wxs - call move /Y ..\..\..\MouseWithoutBorders.wxs.bk ..\..\..\MouseWithoutBorders.wxs - call move /Y ..\..\..\NewPlus.wxs.bk ..\..\..\NewPlus.wxs - call move /Y ..\..\..\Peek.wxs.bk ..\..\..\Peek.wxs - call move /Y ..\..\..\PowerRename.wxs.bk ..\..\..\PowerRename.wxs - call move /Y ..\..\..\Product.wxs.bk ..\..\..\Product.wxs - call move /Y ..\..\..\RegistryPreview.wxs.bk ..\..\..\RegistryPreview.wxs - call move /Y ..\..\..\Resources.wxs.bk ..\..\..\Resources.wxs - call move /Y ..\..\..\Run.wxs.bk ..\..\..\Run.wxs - call move /Y ..\..\..\Settings.wxs.bk ..\..\..\Settings.wxs - call move /Y ..\..\..\ShortcutGuide.wxs.bk ..\..\..\ShortcutGuide.wxs - call move /Y ..\..\..\Tools.wxs.bk ..\..\..\Tools.wxs - call move /Y ..\..\..\WinAppSDK.wxs.bk ..\..\..\WinAppSDK.wxs - call move /Y ..\..\..\WinUI3Applications.wxs.bk ..\..\..\WinUI3Applications.wxs - call move /Y ..\..\..\Workspaces.wxs.bk ..\..\..\Workspaces.wxs - - - - PowerToysInstaller - - - $(DefineConstants);PerUser=true - - - $(DefineConstants);PerUser=false - - - $(DefineConstants);CIBuild=true - - - $(DefineConstants);CIBuild=false - - - - Release - $(Platform) - 3.10 - 022a9d30-7c4f-416d-a9df-5ff2661cc0ad - 2.0 - PowerToysSetup-$(Version)-$(Platform) - PowerToysUserSetup-$(Version)-$(Platform) - Package - True - - - - - ICE91 - 1026;1076 - - - $(Platform)\$(Configuration)\MachineSetup - $(Platform)\$(Configuration)\UserSetup - obj\$(Platform)\$(Configuration)\MachineSetup - obj\$(Platform)\$(Configuration)\UserSetup - ICE40 - - - - - -v -sh -sw1108 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(WixExtDir)\WixFirewallExtension.dll - WixFirewallExtension - - - $(WixExtDir)\WixUtilExtension.dll - WixUtilExtension - - - $(WixExtDir)\WixUIExtension.dll - WixUIExtension - - - $(WixExtDir)\WixNetFxExtension.dll - WixNetFxExtension - - - - - - - - PowerToysSetupCustomActions - {32f3882b-f2d6-4586-b5ed-11e39e522bd3} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - <_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" /> - - - - diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs deleted file mode 100644 index c7f9d3bda4..0000000000 --- a/installer/PowerToysSetup/Product.wxs +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - = 19041)]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - 1 - - NOT Installed - 1 - NOT Installed - NOT Installed - Installed AND _REMOVE_ALL="Yes" - Installed AND _REMOVE_ALL="Yes" - Installed AND NOT (_REMOVE_ALL="Yes") - Installed AND NOT (_REMOVE_ALL="Yes") - - - - - - - - - - - - - - - - - ""]]> - - - DEFAULTBOOTSTRAPPERINSTALLFOLDER OR PREVIOUSINSTALLFOLDER = ""]]> - - - - - - - - - - - - - - - - - - NOT Installed - - - NOT Installed - - - NOT Installed - - - - - - - - - - NOT Installed - - - Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - Installed AND (REMOVE="ALL") - - - Installed AND (REMOVE="ALL") - - - Installed AND (REMOVE="ALL") - - - Installed AND (REMOVE="ALL") - - - Installed AND (REMOVE="ALL") - - - Installed AND (REMOVE="ALL") - - - Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - WIX_UPGRADE_DETECTED - - - Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - - - - Installed AND (REMOVE="ALL") - - - - NOT Installed - - - NOT Installed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/RegistryPreview.wxs b/installer/PowerToysSetup/RegistryPreview.wxs deleted file mode 100644 index f7bd3948d4..0000000000 --- a/installer/PowerToysSetup/RegistryPreview.wxs +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Resources.wxs b/installer/PowerToysSetup/Resources.wxs deleted file mode 100644 index b238799dd1..0000000000 --- a/installer/PowerToysSetup/Resources.wxs +++ /dev/null @@ -1,564 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Run.wxs b/installer/PowerToysSetup/Run.wxs deleted file mode 100644 index 94a589585f..0000000000 --- a/installer/PowerToysSetup/Run.wxs +++ /dev/null @@ -1,448 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Settings.wxs b/installer/PowerToysSetup/Settings.wxs deleted file mode 100644 index 07a2b056dc..0000000000 --- a/installer/PowerToysSetup/Settings.wxs +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/ShortcutGuide.wxs b/installer/PowerToysSetup/ShortcutGuide.wxs deleted file mode 100644 index 729a805861..0000000000 --- a/installer/PowerToysSetup/ShortcutGuide.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Tools.wxs b/installer/PowerToysSetup/Tools.wxs deleted file mode 100644 index 24c3fc2007..0000000000 --- a/installer/PowerToysSetup/Tools.wxs +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/WebView2/MicrosoftEdgeWebview2Setup.exe b/installer/PowerToysSetup/WebView2/MicrosoftEdgeWebview2Setup.exe deleted file mode 100644 index d95ba2e893..0000000000 Binary files a/installer/PowerToysSetup/WebView2/MicrosoftEdgeWebview2Setup.exe and /dev/null differ diff --git a/installer/PowerToysSetup/WinAppSDK.wxs b/installer/PowerToysSetup/WinAppSDK.wxs deleted file mode 100644 index 631fb033ef..0000000000 --- a/installer/PowerToysSetup/WinAppSDK.wxs +++ /dev/null @@ -1,466 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/WinUI3Applications.wxs b/installer/PowerToysSetup/WinUI3Applications.wxs deleted file mode 100644 index 742f3dcf80..0000000000 --- a/installer/PowerToysSetup/WinUI3Applications.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/Workspaces.wxs b/installer/PowerToysSetup/Workspaces.wxs deleted file mode 100644 index 4237aab945..0000000000 --- a/installer/PowerToysSetup/Workspaces.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/installer/PowerToysSetup/generateAllFileComponents.ps1 b/installer/PowerToysSetup/generateAllFileComponents.ps1 deleted file mode 100644 index a8365d8649..0000000000 --- a/installer/PowerToysSetup/generateAllFileComponents.ps1 +++ /dev/null @@ -1,323 +0,0 @@ -[CmdletBinding()] -Param( - [Parameter(Mandatory = $True, Position = 1)] - [string]$platform, - [Parameter(Mandatory = $False, Position = 2)] - [string]$installscopeperuser = "false" -) - -Function Generate-FileList() { - [CmdletBinding()] - Param( - # Can be multiple files separated by ; as long as they're on the same directory - [Parameter(Mandatory = $True, Position = 1)] - [AllowEmptyString()] - [string]$fileDepsJson, - [Parameter(Mandatory = $True, Position = 2)] - [string]$fileListName, - [Parameter(Mandatory = $True, Position = 3)] - [string]$wxsFilePath, - # If there is no deps.json file, just pass path to files - [Parameter(Mandatory = $False, Position = 4)] - [string]$depsPath, - # launcher plugins are being loaded into launcher process, - # so there are some additional dependencies to skip - [Parameter(Mandatory = $False, Position = 5)] - [bool]$isLauncherPlugin - ) - - $fileWxs = Get-Content $wxsFilePath; - - $fileExclusionList = @("*.pdb", "*.lastcodeanalysissucceeded", "createdump.exe", "powertoys.exe") - - $fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri") - - $dllsToIgnore = @("System.CodeDom.dll", "WindowsBase.dll") - - if ($fileDepsJson -eq [string]::Empty) { - $fileDepsRoot = $depsPath - } else { - $multipleDepsJson = $fileDepsJson.Split(";") - - foreach ( $singleDepsJson in $multipleDepsJson ) - { - - $fileDepsRoot = (Get-ChildItem $singleDepsJson).Directory.FullName - $depsJson = Get-Content $singleDepsJson | ConvertFrom-Json - - $runtimeList = ([array]$depsJson.targets.PSObject.Properties)[-1].Value.PSObject.Properties | Where-Object { - $_.Name -match "runtimepack.*Runtime" - }; - - $runtimeList | ForEach-Object { - $_.Value.PSObject.Properties.Value | ForEach-Object { - $fileExclusionList += $_.PSObject.Properties.Name - } - } - } - } - - $fileExclusionList = $fileExclusionList | Where-Object {$_ -notin $dllsToIgnore} - - if ($isLauncherPlugin -eq $True) { - $fileInclusionList += @("*.deps.json") - $fileExclusionList += @("Ijwhost.dll", "PowerToys.Common.UI.dll", "PowerToys.GPOWrapper.dll", "PowerToys.GPOWrapperProjection.dll", "PowerToys.PowerLauncher.Telemetry.dll", "PowerToys.ManagedCommon.dll", "PowerToys.Settings.UI.Lib.dll", "Wox.Infrastructure.dll", "Wox.Plugin.dll") - } - - $fileList = Get-ChildItem $fileDepsRoot -Include $fileInclusionList -Exclude $fileExclusionList -File -Name - - $fileWxs = $fileWxs -replace "(<\?define $($fileListName)=)", "") { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'fileList', - Justification = 'variable is used in another scope')] - - $fileList = $matches[2] -split ';' - return - } - } - - $componentId = "$($fileListName)_Component" - - $componentDefs = "`r`n" - $componentDefs += - @" - - - - `r`n -"@ - - foreach ($file in $fileList) { - $fileTmp = $file -replace "-", "_" - $componentDefs += - @" - `r`n -"@ - } - - $componentDefs += - @" - `r`n -"@ - - $wxsFile = $wxsFile -replace "\s+()", $componentDefs - - $componentRef = - @" - -"@ - - $wxsFile = $wxsFile -replace "\s+()", "$componentRef`r`n " - - Set-Content -Path $wxsFilePath -Value $wxsFile -} - -if ($platform -ceq "arm64") { - $platform = "ARM64" -} - -if ($installscopeperuser -eq "true") { - $registryroot = "HKCU" -} else { - $registryroot = "HKLM" -} - -#BaseApplications -Generate-FileList -fileDepsJson "" -fileListName BaseApplicationsFiles -wxsFilePath $PSScriptRoot\BaseApplications.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release" -Generate-FileComponents -fileListName "BaseApplicationsFiles" -wxsFilePath $PSScriptRoot\BaseApplications.wxs -regroot $registryroot - -#WinUI3Applications -Generate-FileList -fileDepsJson "" -fileListName WinUI3ApplicationsFiles -wxsFilePath $PSScriptRoot\WinUI3Applications.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps" -Generate-FileComponents -fileListName "WinUI3ApplicationsFiles" -wxsFilePath $PSScriptRoot\WinUI3Applications.wxs -regroot $registryroot - -#AdvancedPaste -Generate-FileList -fileDepsJson "" -fileListName AdvancedPasteAssetsFiles -wxsFilePath $PSScriptRoot\AdvancedPaste.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\AdvancedPaste" -Generate-FileComponents -fileListName "AdvancedPasteAssetsFiles" -wxsFilePath $PSScriptRoot\AdvancedPaste.wxs -regroot $registryroot - -#AwakeFiles -Generate-FileList -fileDepsJson "" -fileListName AwakeImagesFiles -wxsFilePath $PSScriptRoot\Awake.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Awake" -Generate-FileComponents -fileListName "AwakeImagesFiles" -wxsFilePath $PSScriptRoot\Awake.wxs -regroot $registryroot - -#ColorPicker -Generate-FileList -fileDepsJson "" -fileListName ColorPickerAssetsFiles -wxsFilePath $PSScriptRoot\ColorPicker.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ColorPicker" -Generate-FileComponents -fileListName "ColorPickerAssetsFiles" -wxsFilePath $PSScriptRoot\ColorPicker.wxs -regroot $registryroot - -#Environment Variables -Generate-FileList -fileDepsJson "" -fileListName EnvironmentVariablesAssetsFiles -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\EnvironmentVariables" -Generate-FileComponents -fileListName "EnvironmentVariablesAssetsFiles" -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -regroot $registryroot - -#FileExplorerAdd-ons -Generate-FileList -fileDepsJson "" -fileListName MonacoPreviewHandlerMonacoAssetsFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco" -Generate-FileList -fileDepsJson "" -fileListName MonacoPreviewHandlerCustomLanguagesFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco\customLanguages" -Generate-FileComponents -fileListName "MonacoPreviewHandlerMonacoAssetsFiles" -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -regroot $registryroot -Generate-FileComponents -fileListName "MonacoPreviewHandlerCustomLanguagesFiles" -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -regroot $registryroot - -#FileLocksmith -Generate-FileList -fileDepsJson "" -fileListName FileLocksmithAssetsFiles -wxsFilePath $PSScriptRoot\FileLocksmith.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\FileLocksmith" -Generate-FileComponents -fileListName "FileLocksmithAssetsFiles" -wxsFilePath $PSScriptRoot\FileLocksmith.wxs -regroot $registryroot - -#Hosts -Generate-FileList -fileDepsJson "" -fileListName HostsAssetsFiles -wxsFilePath $PSScriptRoot\Hosts.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Hosts" -Generate-FileComponents -fileListName "HostsAssetsFiles" -wxsFilePath $PSScriptRoot\Hosts.wxs -regroot $registryroot - -#ImageResizer -Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFilePath $PSScriptRoot\ImageResizer.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ImageResizer" -Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs -regroot $registryroot - -#New+ -Generate-FileList -fileDepsJson "" -fileListName NewPlusAssetsFiles -wxsFilePath $PSScriptRoot\NewPlus.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\NewPlus" -Generate-FileComponents -fileListName "NewPlusAssetsFiles" -wxsFilePath $PSScriptRoot\NewPlus.wxs -regroot $registryroot - -#Peek -Generate-FileList -fileDepsJson "" -fileListName PeekAssetsFiles -wxsFilePath $PSScriptRoot\Peek.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Peek\" -Generate-FileComponents -fileListName "PeekAssetsFiles" -wxsFilePath $PSScriptRoot\Peek.wxs -regroot $registryroot - -#PowerRename -Generate-FileList -fileDepsJson "" -fileListName PowerRenameAssetsFiles -wxsFilePath $PSScriptRoot\PowerRename.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\PowerRename\" -Generate-FileComponents -fileListName "PowerRenameAssetsFiles" -wxsFilePath $PSScriptRoot\PowerRename.wxs -regroot $registryroot - -#RegistryPreview -Generate-FileList -fileDepsJson "" -fileListName RegistryPreviewAssetsFiles -wxsFilePath $PSScriptRoot\RegistryPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\RegistryPreview\" -Generate-FileComponents -fileListName "RegistryPreviewAssetsFiles" -wxsFilePath $PSScriptRoot\RegistryPreview.wxs -regroot $registryroot - -#Run -Generate-FileList -fileDepsJson "" -fileListName launcherImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\PowerLauncher" -Generate-FileComponents -fileListName "launcherImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -## Plugins -###Calculator -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.deps.json" -fileListName calcComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName calcImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Calculator\Images" -Generate-FileComponents -fileListName "calcComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "calcImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Folder -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Folder\Microsoft.Plugin.Folder.deps.json" -fileListName FolderComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName FolderImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Folder\Images" -Generate-FileComponents -fileListName "FolderComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "FolderImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Program -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Program\Microsoft.Plugin.Program.deps.json" -fileListName ProgramComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName ProgramImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Program\Images" -Generate-FileComponents -fileListName "ProgramComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "ProgramImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Shell -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Shell\Microsoft.Plugin.Shell.deps.json" -fileListName ShellComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName ShellImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Shell\Images" -Generate-FileComponents -fileListName "ShellComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "ShellImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Indexer -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Indexer\Microsoft.Plugin.Indexer.deps.json" -fileListName IndexerComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName IndexerImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Indexer\Images" -Generate-FileComponents -fileListName "IndexerComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "IndexerImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###UnitConverter -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.deps.json" -fileListName UnitConvCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName UnitConvImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\UnitConverter\Images" -Generate-FileComponents -fileListName "UnitConvCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "UnitConvImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###WebSearch -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WebSearch\Community.PowerToys.Run.Plugin.WebSearch.deps.json" -fileListName WebSrchCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName WebSrchImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WebSearch\Images" -Generate-FileComponents -fileListName "WebSrchCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "WebSrchImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###History -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\History\Microsoft.PowerToys.Run.Plugin.History.deps.json" -fileListName HistoryPluginComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName HistoryPluginImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\History\Images" -Generate-FileComponents -fileListName "HistoryPluginComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "HistoryPluginImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Uri -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Uri\Microsoft.Plugin.Uri.deps.json" -fileListName UriComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName UriImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Uri\Images" -Generate-FileComponents -fileListName "UriComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "UriImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###VSCodeWorkspaces -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\VSCodeWorkspaces\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.deps.json" -fileListName VSCWrkCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName VSCWrkImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\VSCodeWorkspaces\Images" -Generate-FileComponents -fileListName "VSCWrkCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "VSCWrkImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###WindowWalker -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowWalker\Microsoft.Plugin.WindowWalker.deps.json" -fileListName WindowWlkrCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName WindowWlkrImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowWalker\Images" -Generate-FileComponents -fileListName "WindowWlkrCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "WindowWlkrImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###OneNote -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\OneNote\Microsoft.PowerToys.Run.Plugin.OneNote.deps.json" -fileListName OneNoteComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName OneNoteImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\OneNote\Images" -Generate-FileComponents -fileListName "OneNoteComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "OneNoteImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Registry -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Registry\Microsoft.PowerToys.Run.Plugin.Registry.deps.json" -fileListName RegistryComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName RegistryImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Registry\Images" -Generate-FileComponents -fileListName "RegistryComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "RegistryImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###Service -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Service\Microsoft.PowerToys.Run.Plugin.Service.deps.json" -fileListName ServiceComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName ServiceImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Service\Images" -Generate-FileComponents -fileListName "ServiceComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "ServiceImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###System -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\System\Microsoft.PowerToys.Run.Plugin.System.deps.json" -fileListName SystemComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName SystemImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\System\Images" -Generate-FileComponents -fileListName "SystemComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "SystemImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###TimeDate -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\TimeDate\Microsoft.PowerToys.Run.Plugin.TimeDate.deps.json" -fileListName TimeDateComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName TimeDateImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\TimeDate\Images" -Generate-FileComponents -fileListName "TimeDateComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "TimeDateImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###WindowsSettings -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsSettings\Microsoft.PowerToys.Run.Plugin.WindowsSettings.deps.json" -fileListName WinSetCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName WinSetImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsSettings\Images" -Generate-FileComponents -fileListName "WinSetCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "WinSetImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###WindowsTerminal -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.deps.json" -fileListName WinTermCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName WinTermImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsTerminal\Images" -Generate-FileComponents -fileListName "WinTermCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "WinTermImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###PowerToys -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.deps.json" -fileListName PowerToysCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName PowerToysImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\PowerToys\Images" -Generate-FileComponents -fileListName "PowerToysCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "PowerToysImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -###ValueGenerator -Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.deps.json" -fileListName ValueGeneratorCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1 -Generate-FileList -fileDepsJson "" -fileListName ValueGeneratorImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\ValueGenerator\Images" -Generate-FileComponents -fileListName "ValueGeneratorCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot -## Plugins - -#ShortcutGuide -Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideSvgFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ShortcutGuide\" -Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot - -#Settings -Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\" -Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\" -Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\OOBE\" -Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsFluentIconsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\" -Generate-FileComponents -fileListName "SettingsV2AssetsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot -Generate-FileComponents -fileListName "SettingsV2AssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot -Generate-FileComponents -fileListName "SettingsV2OOBEAssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot -Generate-FileComponents -fileListName "SettingsV2OOBEAssetsFluentIconsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot - -#Workspaces -Generate-FileList -fileDepsJson "" -fileListName WorkspacesImagesComponentFiles -wxsFilePath $PSScriptRoot\Workspaces.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Workspaces\" -Generate-FileComponents -fileListName "WorkspacesImagesComponentFiles" -wxsFilePath $PSScriptRoot\Workspaces.wxs -regroot $registryroot diff --git a/installer/PowerToysSetup/generateMonacoWxs.ps1 b/installer/PowerToysSetup/generateMonacoWxs.ps1 deleted file mode 100644 index 94536618da..0000000000 --- a/installer/PowerToysSetup/generateMonacoWxs.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -[CmdletBinding()] -Param( - [Parameter(Mandatory = $True, Position = 1)] - [string]$monacoWxsFile -) - -$fileWxs = Get-Content $monacoWxsFile; - -$fileWxs = $fileWxs -replace " KeyPath=`"yes`" ", " " - -$newFileContent = "" - -$componentId = "error" -$directories = @() - -$fileWxs | ForEach-Object { - $line = $_; - if ($line -match "") { - $line += -@" -`r`n - `r`n -"@ - } - if ($line -match "") { - $directories += $matches[1] - } - if ($line -match "") { - $line = -@" - - - - -"@ - } - - $newFileContent += $line + "`r`n"; -} - -$removeFolderEntries = -@" -`r`n - - - `r`n -"@ - -$directories | ForEach-Object { - - $removeFolderEntries += -@" - - -"@ -} - -$removeFolderEntries += -@" - -"@ - - - -$newFileContent = $newFileContent -replace "\s+()", "$removeFolderEntries`r`n " - -Set-Content -Path $monacoWxsFile -Value $newFileContent \ No newline at end of file diff --git a/installer/PowerToysSetup/packages.config b/installer/PowerToysSetup/packages.config deleted file mode 100644 index 569e1bea86..0000000000 --- a/installer/PowerToysSetup/packages.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/installer/PowerToysSetup/publish.cmd b/installer/PowerToysSetup/publish.cmd deleted file mode 100644 index f61668cffe..0000000000 --- a/installer/PowerToysSetup/publish.cmd +++ /dev/null @@ -1,17 +0,0 @@ -setlocal enableDelayedExpansion - -IF NOT DEFINED PTRoot (SET PTRoot=..\..) - -SET PlatformArg=%1 -IF NOT DEFINED PlatformArg (SET PlatformArg=x64) -SET VCToolsVersion=!VCToolsVersion! -SET ClearDevCommandPromptEnvVars=false - -rem In case of Release we should not use Debug CRT in VCRT forwarders -msbuild !PTRoot!\src\modules\previewpane\MonacoPreviewHandler\MonacoPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0 - -msbuild !PTRoot!\src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0 - -msbuild !PTRoot!\src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0 - -msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0 \ No newline at end of file diff --git a/installer/PowerToysSetup/terminate_powertoys.cmd b/installer/PowerToysSetup/terminate_powertoys.cmd deleted file mode 100644 index 8206b90aae..0000000000 --- a/installer/PowerToysSetup/terminate_powertoys.cmd +++ /dev/null @@ -1,12 +0,0 @@ -@echo off -setlocal ENABLEDELAYEDEXPANSION - -@REM We loop here until taskkill cannot find a PowerToys process. We can't use /F flag, because it -@REM doesn't give application an opportunity to cleanup. Thus we send WM_CLOSE which is being caught -@REM by multiple windows running a msg loop in PowerToys.exe process, which we close one by one. -for /l %%x in (1, 1, 100) do ( - taskkill /IM PowerToys.exe 1>NUL 2>NUL - if !ERRORLEVEL! NEQ 0 goto quit -) - -:quit \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp deleted file mode 100644 index 74304c4163..0000000000 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ /dev/null @@ -1,1410 +0,0 @@ -#include "pch.h" -#include "resource.h" -#include "RcResource.h" -#include -#include - -#include "../../src/common/logger/logger.h" -#include "../../src/common/utils/gpo.h" -#include "../../src/common/utils/MsiUtils.h" -#include "../../src/common/utils/modulesRegistry.h" -#include "../../src/common/updating/installer.h" -#include "../../src/common/version/version.h" -#include "../../src/common/Telemetry/EtwTrace/EtwTrace.h" -#include "../../src/common/utils/package.h" -#include "../../src/common/utils/clean_video_conference.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace std; - -HINSTANCE DLL_HANDLE = nullptr; - -TRACELOGGING_DEFINE_PROVIDER( - g_hProvider, - "Microsoft.PowerToys", - // {38e8889b-9731-53f5-e901-e8a7c1753074} - (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), - TraceLoggingOptionProjectTelemetry()); - -const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0' -const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' - -static const wchar_t *POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}"; -static const wchar_t *POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}"; - -constexpr inline const wchar_t *DataDiagnosticsRegKey = L"Software\\Classes\\PowerToys"; -constexpr inline const wchar_t *DataDiagnosticsRegValueName = L"AllowDataDiagnostics"; - -#define TraceLoggingWriteWrapper(provider, eventName, ...) \ - if (isDataDiagnosticEnabled()) \ - { \ - trace.UpdateState(true); \ - TraceLoggingWrite(provider, eventName, __VA_ARGS__); \ - trace.Flush(); \ - trace.UpdateState(false); \ - } - -static Shared::Trace::ETWTrace trace{L"PowerToys_Installer"}; - -inline bool isDataDiagnosticEnabled() -{ - HKEY key{}; - if (RegOpenKeyExW(HKEY_CURRENT_USER, - DataDiagnosticsRegKey, - 0, - KEY_READ, - &key) != ERROR_SUCCESS) - { - return false; - } - - DWORD isDataDiagnosticsEnabled = 0; - DWORD size = sizeof(isDataDiagnosticsEnabled); - - if (RegGetValueW( - HKEY_CURRENT_USER, - DataDiagnosticsRegKey, - DataDiagnosticsRegValueName, - RRF_RT_REG_DWORD, - nullptr, - &isDataDiagnosticsEnabled, - &size) != ERROR_SUCCESS) - { - RegCloseKey(key); - return false; - } - RegCloseKey(key); - - return isDataDiagnosticsEnabled == 1; -} - -HRESULT getInstallFolder(MSIHANDLE hInstall, std::wstring &installationDir) -{ - DWORD len = 0; - wchar_t _[1]; - MsiGetPropertyW(hInstall, L"CustomActionData", _, &len); - len += 1; - installationDir.resize(len); - HRESULT hr = MsiGetPropertyW(hInstall, L"CustomActionData", installationDir.data(), &len); - if (installationDir.length()) - { - installationDir.resize(installationDir.length() - 1); - } - ExitOnFailure(hr, "Failed to get INSTALLFOLDER property."); -LExit: - return hr; -} - -BOOL IsLocalSystem() -{ - HANDLE hToken; - UCHAR bTokenUser[sizeof(TOKEN_USER) + 8 + 4 * SID_MAX_SUB_AUTHORITIES]; - PTOKEN_USER pTokenUser = (PTOKEN_USER)bTokenUser; - ULONG cbTokenUser; - SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY; - PSID pSystemSid; - BOOL bSystem; - - // open process token - if (!OpenProcessToken(GetCurrentProcess(), - TOKEN_QUERY, - &hToken)) - return FALSE; - - // retrieve user SID - if (!GetTokenInformation(hToken, TokenUser, pTokenUser, - sizeof(bTokenUser), &cbTokenUser)) - { - CloseHandle(hToken); - return FALSE; - } - - CloseHandle(hToken); - - // allocate LocalSystem well-known SID - if (!AllocateAndInitializeSid(&siaNT, 1, SECURITY_LOCAL_SYSTEM_RID, - 0, 0, 0, 0, 0, 0, 0, &pSystemSid)) - return FALSE; - - // compare the user SID from the token with the LocalSystem SID - bSystem = EqualSid(pTokenUser->User.Sid, pSystemSid); - - FreeSid(pSystemSid); - - return bSystem; -} - -BOOL ImpersonateLoggedInUserAndDoSomething(std::function action) -{ - HRESULT hr = S_OK; - HANDLE hUserToken = NULL; - DWORD dwSessionId; - ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId); - auto rv = WTSQueryUserToken(dwSessionId, &hUserToken); - - if (rv == 0) - { - hr = E_ABORT; - ExitOnFailure(hr, "Failed to query user token"); - } - - HANDLE hUserTokenDup; - if (DuplicateTokenEx(hUserToken, TOKEN_ALL_ACCESS, NULL, SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation, TOKEN_TYPE::TokenPrimary, &hUserTokenDup) == 0) - { - CloseHandle(hUserToken); - CloseHandle(hUserTokenDup); - hr = E_ABORT; - ExitOnFailure(hr, "Failed to duplicate user token"); - } - - if (ImpersonateLoggedOnUser(hUserTokenDup)) - { - if (!action(hUserTokenDup)) - { - hr = E_ABORT; - ExitOnFailure(hr, "Failed to execute action"); - } - - RevertToSelf(); - CloseHandle(hUserToken); - CloseHandle(hUserTokenDup); - } - else - { - hr = E_ABORT; - ExitOnFailure(hr, "Failed to duplicate user token"); - } - -LExit: - return SUCCEEDED(hr); -} - -static std::filesystem::path GetUserPowerShellModulesPath() -{ - PWSTR myDocumentsBlockPtr; - - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &myDocumentsBlockPtr))) - { - const std::wstring myDocuments{myDocumentsBlockPtr}; - CoTaskMemFree(myDocumentsBlockPtr); - return std::filesystem::path(myDocuments) / "PowerShell" / "Modules"; - } - else - { - CoTaskMemFree(myDocumentsBlockPtr); - return {}; - } -} - -UINT __stdcall LaunchPowerToysCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder, path, args; - std::wstring commandLine; - - hr = WcaInitialize(hInstall, "LaunchPowerToys"); - ExitOnFailure(hr, "Failed to initialize"); - hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installFolder."); - - path = installationFolder; - path += L"\\PowerToys.exe"; - - args = L"--dont-elevate"; - - commandLine = L"\"" + path + L"\" "; - commandLine += args; - - BOOL isSystemUser = IsLocalSystem(); - - if (isSystemUser) - { - - auto action = [&commandLine](HANDLE userToken) - { - STARTUPINFO startupInfo{.cb = sizeof(STARTUPINFO), .wShowWindow = SW_SHOWNORMAL}; - PROCESS_INFORMATION processInformation; - - PVOID lpEnvironment = NULL; - CreateEnvironmentBlock(&lpEnvironment, userToken, FALSE); - - CreateProcessAsUser( - userToken, - NULL, - commandLine.data(), - NULL, - NULL, - FALSE, - CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT, - lpEnvironment, - NULL, - &startupInfo, - &processInformation); - - if (!CloseHandle(processInformation.hProcess)) - { - return false; - } - if (!CloseHandle(processInformation.hThread)) - { - return false; - } - - return true; - }; - - if (!ImpersonateLoggedInUserAndDoSomething(action)) - { - hr = E_ABORT; - ExitOnFailure(hr, "ImpersonateLoggedInUserAndDoSomething failed"); - } - } - else - { - STARTUPINFO startupInfo{.cb = sizeof(STARTUPINFO), .wShowWindow = SW_SHOWNORMAL}; - - PROCESS_INFORMATION processInformation; - - // Start the resizer - CreateProcess( - NULL, - commandLine.data(), - NULL, - NULL, - TRUE, - 0, - NULL, - NULL, - &startupInfo, - &processInformation); - - if (!CloseHandle(processInformation.hProcess)) - { - hr = E_ABORT; - ExitOnFailure(hr, "Failed to close process handle"); - } - if (!CloseHandle(processInformation.hThread)) - { - hr = E_ABORT; - ExitOnFailure(hr, "Failed to close thread handle"); - } - } - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall CheckGPOCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - - hr = WcaInitialize(hInstall, "CheckGPOCA"); - ExitOnFailure(hr, "Failed to initialize"); - - LPWSTR currentScope = nullptr; - hr = WcaGetProperty(L"InstallScope", ¤tScope); - - if (std::wstring{currentScope} == L"perUser") - { - if (powertoys_gpo::getDisablePerUserInstallationValue() == powertoys_gpo::gpo_rule_configured_enabled) - { - PMSIHANDLE hRecord = MsiCreateRecord(0); - MsiRecordSetString(hRecord, 0, TEXT("The system administrator has disabled per-user installation.")); - MsiProcessMessage(hInstall, static_cast(INSTALLMESSAGE_ERROR + MB_OK), hRecord); - hr = E_ABORT; - } - } - -LExit: - UINT er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -// We've deprecated Video Conference Mute. This Custom Action cleans up any stray registry entry for the driver dll. -UINT __stdcall CleanVideoConferenceRegistryCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "CleanVideoConferenceRegistry"); - ExitOnFailure(hr, "Failed to initialize"); - clean_video_conference(); -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall ApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder; - bool failedToApply = false; - - hr = WcaInitialize(hInstall, "ApplyModulesRegistryChangeSets"); - ExitOnFailure(hr, "Failed to initialize"); - hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installFolder."); - - for (const auto &changeSet : getAllOnByDefaultModulesChangeSets(installationFolder)) - { - if (!changeSet.apply()) - { - Logger::error(L"Couldn't apply registry changeSet"); - failedToApply = true; - } - } - - if (!failedToApply) - { - Logger::info(L"All registry changeSets applied successfully"); - } -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall UnApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder; - - hr = WcaInitialize(hInstall, "UndoModulesRegistryChangeSets"); // original func name is too long - ExitOnFailure(hr, "Failed to initialize"); - hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installFolder."); - for (const auto &changeSet : getAllModulesChangeSets(installationFolder)) - { - changeSet.unApply(); - } - - SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); - - ExitOnFailure(hr, "Failed to extract msix"); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -const wchar_t *DSC_CONFIGURE_PSD1_NAME = L"Microsoft.PowerToys.Configure.psd1"; -const wchar_t *DSC_CONFIGURE_PSM1_NAME = L"Microsoft.PowerToys.Configure.psm1"; - -UINT __stdcall InstallDSCModuleCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder; - - hr = WcaInitialize(hInstall, "InstallDSCModuleCA"); - ExitOnFailure(hr, "Failed to initialize"); - - hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installFolder."); - - { - const auto baseModulesPath = GetUserPowerShellModulesPath(); - if (baseModulesPath.empty()) - { - hr = E_FAIL; - ExitOnFailure(hr, "Unable to determine Powershell modules path"); - } - - const auto modulesPath = baseModulesPath / L"Microsoft.PowerToys.Configure" / (get_product_version(false) + L".0"); - - std::error_code errorCode; - fs::create_directories(modulesPath, errorCode); - if (errorCode) - { - hr = E_FAIL; - ExitOnFailure(hr, "Unable to create Powershell modules folder"); - } - - 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); - - if (errorCode) - { - hr = E_FAIL; - ExitOnFailure(hr, "Unable to copy Powershell modules file"); - } - } - } - -LExit: - if (SUCCEEDED(hr)) - { - er = ERROR_SUCCESS; - Logger::info(L"DSC module was installed!"); - } - else - { - er = ERROR_INSTALL_FAILURE; - Logger::error(L"Couldn't install DSC module!"); - } - - return WcaFinalize(er); -} - -UINT __stdcall UninstallDSCModuleCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "UninstallDSCModuleCA"); - ExitOnFailure(hr, "Failed to initialize"); - - { - const auto baseModulesPath = GetUserPowerShellModulesPath(); - if (baseModulesPath.empty()) - { - hr = E_FAIL; - ExitOnFailure(hr, "Unable to determine Powershell modules path"); - } - - const auto powerToysModulePath = baseModulesPath / L"Microsoft.PowerToys.Configure"; - const auto versionedModulePath = powerToysModulePath / (get_product_version(false) + L".0"); - - std::error_code errorCode; - - for (const auto *filename : {DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME}) - { - fs::remove(versionedModulePath / filename, errorCode); - - if (errorCode) - { - hr = E_FAIL; - ExitOnFailure(hr, "Unable to delete DSC file"); - } - } - - for (const auto *modulePath : {&versionedModulePath, &powerToysModulePath}) - { - fs::remove(*modulePath, errorCode); - - if (errorCode) - { - hr = E_FAIL; - ExitOnFailure(hr, "Unable to delete DSC folder"); - } - } - } - -LExit: - if (SUCCEEDED(hr)) - { - er = ERROR_SUCCESS; - Logger::info(L"DSC module was uninstalled!"); - } - else - { - er = ERROR_INSTALL_FAILURE; - Logger::error(L"Couldn't uninstall DSC module!"); - } - - return WcaFinalize(er); -} - -UINT __stdcall InstallEmbeddedMSIXCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "InstallEmbeddedMSIXCA"); - ExitOnFailure(hr, "Failed to initialize"); - - if (auto msix = RcResource::create(IDR_BIN_MSIX_HELLO_PACKAGE, L"BIN", DLL_HANDLE)) - { - Logger::info(L"Extracted MSIX"); - // TODO: Use to activate embedded MSIX - const auto msix_path = std::filesystem::temp_directory_path() / "hello_package.msix"; - if (!msix->saveAsFile(msix_path)) - { - ExitOnFailure(hr, "Failed to save msix"); - } - Logger::info(L"Saved MSIX"); - using namespace winrt::Windows::Management::Deployment; - using namespace winrt::Windows::Foundation; - - Uri msix_uri{msix_path.wstring()}; - PackageManager pm; - auto result = pm.AddPackageAsync(msix_uri, nullptr, DeploymentOptions::None).get(); - if (!result) - { - ExitOnFailure(hr, "Failed to AddPackage"); - } - - Logger::info(L"MSIX[s] were installed!"); - } - else - { - ExitOnFailure(hr, "Failed to extract msix"); - } - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall UninstallEmbeddedMSIXCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - using namespace winrt::Windows::Management::Deployment; - using namespace winrt::Windows::Foundation; - // TODO: This must be replaced with the actual publisher and package name - const wchar_t package_name[] = L"46b35c25-b593-48d5-aeb1-d3e9c3b796e9"; - const wchar_t publisher[] = L"CN=yuyoyuppe"; - PackageManager pm; - - hr = WcaInitialize(hInstall, "UninstallEmbeddedMSIXCA"); - ExitOnFailure(hr, "Failed to initialize"); - - for (const auto &p : pm.FindPackagesForUser({}, package_name, publisher)) - { - auto result = pm.RemovePackageAsync(p.Id().FullName()).get(); - if (result) - { - Logger::info(L"MSIX was uninstalled!"); - } - else - { - Logger::error(L"Couldn't uninstall MSIX!"); - } - } - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName) -{ - SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); - - if (!hSCManager) - { - return ERROR_INSTALL_FAILURE; - } - - SC_HANDLE hService = OpenService(hSCManager, serviceName.c_str(), SERVICE_STOP | DELETE); - if (!hService) - { - CloseServiceHandle(hSCManager); - return ERROR_INSTALL_FAILURE; - } - - SERVICE_STATUS ss; - if (ControlService(hService, SERVICE_CONTROL_STOP, &ss)) - { - Sleep(1000); - while (QueryServiceStatus(hService, &ss)) - { - if (ss.dwCurrentState == SERVICE_STOP_PENDING) - { - Sleep(1000); - } - else - { - break; - } - } - } - - BOOL deleteResult = DeleteService(hService); - CloseServiceHandle(hService); - CloseServiceHandle(hSCManager); - - if (!deleteResult) - { - return ERROR_INSTALL_FAILURE; - } - - return ERROR_SUCCESS; -} - -UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - try - { - winrt::Windows::Security::Credentials::PasswordVault vault; - winrt::Windows::Security::Credentials::PasswordCredential cred; - - hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey"); - ExitOnFailure(hr, "Failed to initialize"); - - cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey"); - vault.Remove(cred); - } - catch (...) - { - } - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall UninstallCommandNotFoundModuleCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder; - std::string command; - - hr = WcaInitialize(hInstall, "UninstallCommandNotFoundModule"); - ExitOnFailure(hr, "Failed to initialize"); - - hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installFolder."); - -#ifdef _M_ARM64 - command = "powershell.exe"; - command += " "; - command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted"; - command += " -Command "; - command += "\"[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('PATH', 'User'), 'Process');"; - command += "pwsh.exe -NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File '" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "'\""; -#else - command = "pwsh.exe"; - command += " "; - command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "\""; -#endif - - system(command.c_str()); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall UpgradeCommandNotFoundModuleCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder; - std::string command; - - hr = WcaInitialize(hInstall, "UpgradeCommandNotFoundModule"); - ExitOnFailure(hr, "Failed to initialize"); - - hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installFolder."); - - command = "pwsh.exe"; - command += " "; - command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\UpgradeModule.ps1" + "\""; - - system(command.c_str()); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall UninstallServicesCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "UninstallServicesCA"); - - ExitOnFailure(hr, "Failed to initialize"); - - hr = RemoveWindowsServiceByName(L"PowerToys.MWB.Service"); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -// Removes all Scheduled Tasks in the PowerToys folder and deletes the folder afterwards. -// Based on the Task Scheduler Displaying Task Names and State example: -// https://learn.microsoft.com/windows/desktop/TaskSchd/displaying-task-names-and-state--c---/ -UINT __stdcall RemoveScheduledTasksCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - ITaskService *pService = nullptr; - ITaskFolder *pTaskFolder = nullptr; - IRegisteredTaskCollection *pTaskCollection = nullptr; - ITaskFolder *pRootFolder = nullptr; - LONG numTasks = 0; - - hr = WcaInitialize(hInstall, "RemoveScheduledTasksCA"); - ExitOnFailure(hr, "Failed to initialize"); - - Logger::info(L"RemoveScheduledTasksCA Initialized."); - - // COM and Security Initialization is expected to have been done by the MSI. - // It couldn't be done in the DLL, anyway. - // ------------------------------------------------------ - // Create an instance of the Task Service. - hr = CoCreateInstance(CLSID_TaskScheduler, - nullptr, - CLSCTX_INPROC_SERVER, - IID_ITaskService, - reinterpret_cast(&pService)); - ExitOnFailure(hr, "Failed to create an instance of ITaskService: %x", hr); - - // Connect to the task service. - hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t()); - ExitOnFailure(hr, "ITaskService::Connect failed: %x", hr); - - // ------------------------------------------------------ - // Get the PowerToys task folder. - hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder); - if (FAILED(hr)) - { - // Folder doesn't exist. No need to delete anything. - Logger::info(L"The PowerToys scheduled task folder wasn't found. Nothing to delete."); - hr = S_OK; - ExitFunction(); - } - - // ------------------------------------------------------- - // Get the registered tasks in the folder. - hr = pTaskFolder->GetTasks(TASK_ENUM_HIDDEN, &pTaskCollection); - ExitOnFailure(hr, "Cannot get the registered tasks: %x", hr); - - hr = pTaskCollection->get_Count(&numTasks); - for (LONG i = 0; i < numTasks; i++) - { - // Delete all the tasks found. - // If some tasks can't be deleted, the folder won't be deleted later and the user will still be notified. - IRegisteredTask *pRegisteredTask = nullptr; - hr = pTaskCollection->get_Item(_variant_t(i + 1), &pRegisteredTask); - if (SUCCEEDED(hr)) - { - BSTR taskName = nullptr; - hr = pRegisteredTask->get_Name(&taskName); - if (SUCCEEDED(hr)) - { - hr = pTaskFolder->DeleteTask(taskName, 0); - if (FAILED(hr)) - { - Logger::error(L"Cannot delete the {} task: {}", taskName, hr); - } - SysFreeString(taskName); - } - else - { - Logger::error(L"Cannot get the registered task name: {}", hr); - } - pRegisteredTask->Release(); - } - else - { - Logger::error(L"Cannot get the registered task item at index={}: {}", i + 1, hr); - } - } - - // ------------------------------------------------------ - // Get the pointer to the root task folder and delete the PowerToys subfolder. - hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder); - ExitOnFailure(hr, "Cannot get Root Folder pointer: %x", hr); - hr = pRootFolder->DeleteFolder(_bstr_t(L"PowerToys"), 0); - pRootFolder->Release(); - ExitOnFailure(hr, "Cannot delete the PowerToys folder: %x", hr); - - Logger::info(L"Deleted the PowerToys Task Scheduler folder."); - -LExit: - if (pService) - { - pService->Release(); - } - if (pTaskFolder) - { - pTaskFolder->Release(); - } - if (pTaskCollection) - { - pTaskCollection->Release(); - } - - if (!SUCCEEDED(hr)) - { - PMSIHANDLE hRecord = MsiCreateRecord(0); - MsiRecordSetString(hRecord, 0, TEXT("Failed to remove the PowerToys folder from the scheduled task. These can be removed manually later.")); - MsiProcessMessage(hInstall, static_cast(INSTALLMESSAGE_WARNING + MB_OK), hRecord); - } - - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogInstallSuccessCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogInstallSuccessCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "Install_Success", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogInstallCancelCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogInstallCancelCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "Install_Cancel", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogInstallFailCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogInstallFailCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "Install_Fail", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogUninstallSuccessCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogUninstallSuccessCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "UnInstall_Success", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogUninstallCancelCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogUninstallCancelCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "UnInstall_Cancel", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogUninstallFailCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogUninstallFailCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "UnInstall_Fail", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogRepairCancelCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogRepairCancelCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "Repair_Cancel", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall TelemetryLogRepairFailCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "TelemetryLogRepairFailCA"); - ExitOnFailure(hr, "Failed to initialize"); - - TraceLoggingWriteWrapper( - g_hProvider, - "Repair_Fail", - TraceLoggingWideString(get_product_version().c_str(), "Version"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); - -LExit: - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "DetectPrevInstallPathCA"); - MsiSetPropertyW(hInstall, L"PREVIOUSINSTALLFOLDER", L""); - - LPWSTR currentScope = nullptr; - hr = WcaGetProperty(L"InstallScope", ¤tScope); - - try - { - if (auto install_path = GetMsiPackageInstalledPath(std::wstring{currentScope} == L"perUser")) - { - MsiSetPropertyW(hInstall, L"PREVIOUSINSTALLFOLDER", install_path->data()); - } - } - catch (...) - { - } - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -UINT __stdcall InstallCmdPalPackageCA(MSIHANDLE hInstall) -{ - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Management::Deployment; - - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - std::wstring installationFolder; - - hr = WcaInitialize(hInstall, "InstallCmdPalPackage"); - hr = getInstallFolder(hInstall, installationFolder); - - try - { - auto msix = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\", false); - auto dependencies = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\Dependencies\\", true); - - if (!msix.empty()) - { - auto msixPath = msix[0]; - - if (!package::RegisterPackage(msixPath, dependencies)) - { - Logger::error(L"Failed to install CmdPal package"); - er = ERROR_INSTALL_FAILURE; - } - } - } - catch (std::exception &e) - { - std::string errorMessage{"Exception thrown while trying to install CmdPal package: "}; - errorMessage += e.what(); - Logger::error(errorMessage); - - er = ERROR_INSTALL_FAILURE; - } - - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - -UINT __stdcall UnRegisterCmdPalPackageCA(MSIHANDLE hInstall) -{ - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Management::Deployment; - - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "UnRegisterCmdPalPackageCA"); - - try - { - // Packages to unregister - std::wstring packageToRemoveDisplayName {L"Microsoft.CommandPalette"}; - - if (!package::UnRegisterPackage(packageToRemoveDisplayName)) - { - Logger::error(L"Failed to unregister package: " + packageToRemoveDisplayName); - er = ERROR_INSTALL_FAILURE; - } - } - catch (std::exception &e) - { - std::string errorMessage{"Exception thrown while trying to unregister the CmdPal package: "}; - errorMessage += e.what(); - Logger::error(errorMessage); - - er = ERROR_INSTALL_FAILURE; - } - - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - - -UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall) -{ - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Management::Deployment; - - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "UnRegisterContextMenuPackagesCA"); // original func name is too long - - try - { - // Packages to unregister - const std::vector packagesToRemoveDisplayName{{L"PowerRenameContextMenu"}, {L"ImageResizerContextMenu"}, {L"FileLocksmithContextMenu"}, {L"NewPlusContextMenu"}}; - - for (auto const &package : packagesToRemoveDisplayName) - { - if (!package::UnRegisterPackage(package)) - { - Logger::error(L"Failed to unregister package: " + package); - er = ERROR_INSTALL_FAILURE; - } - } - } - catch (std::exception &e) - { - std::string errorMessage{"Exception thrown while trying to unregister sparse packages: "}; - errorMessage += e.what(); - Logger::error(errorMessage); - - er = ERROR_INSTALL_FAILURE; - } - - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - -UINT __stdcall CleanImageResizerRuntimeRegistryCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "CleanImageResizerRuntimeRegistryCA"); - - try - { - const wchar_t* CLSID_STR = L"{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"; - const wchar_t* exts[] = { L".bmp", L".dib", L".gif", L".jfif", L".jpe", L".jpeg", L".jpg", L".jxr", L".png", L".rle", L".tif", L".tiff", L".wdp" }; - - auto deleteKeyRecursive = [](HKEY root, const std::wstring &path) { - RegDeleteTreeW(root, path.c_str()); - }; - - // InprocServer32 chain root CLSID - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR)); - // DragDrop handler - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\ShellEx\\DragDropHandlers\\ImageResizer"); - // Extensions - for (auto ext : exts) - { - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\SystemFileAssociations\\" + std::wstring(ext) + L"\\ShellEx\\ContextMenuHandlers\\ImageResizer"); - } - // Sentinel - RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\ImageResizer"); - } - catch (...) - { - er = ERROR_INSTALL_FAILURE; - } - - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - -UINT __stdcall CleanFileLocksmithRuntimeRegistryCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "CleanFileLocksmithRuntimeRegistryCA"); - try - { - const wchar_t* CLSID_STR = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"; - auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) { - RegDeleteTreeW(root, path.c_str()); - }; - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR)); - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt"); - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt"); - RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\FileLocksmith"); - } - catch (...) - { - er = ERROR_INSTALL_FAILURE; - } - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - -UINT __stdcall CleanPowerRenameRuntimeRegistryCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "CleanPowerRenameRuntimeRegistryCA"); - try - { - const wchar_t* CLSID_STR = L"{0440049F-D1DC-4E46-B27B-98393D79486B}"; - auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) { - RegDeleteTreeW(root, path.c_str()); - }; - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR)); - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt"); - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\PowerRenameExt"); - RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\PowerRename"); - } - catch (...) - { - er = ERROR_INSTALL_FAILURE; - } - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - -UINT __stdcall CleanNewPlusRuntimeRegistryCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "CleanNewPlusRuntimeRegistryCA"); - try - { - const wchar_t* CLSID_STR = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}"; - auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) { - RegDeleteTreeW(root, path.c_str()); - }; - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR)); - deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10"); - RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\NewPlus"); - } - catch (...) - { - er = ERROR_INSTALL_FAILURE; - } - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); -} - -UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) -{ - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - hr = WcaInitialize(hInstall, "TerminateProcessesCA"); - - std::vector processes; - const size_t maxProcesses = 4096; - DWORD bytes = maxProcesses * sizeof(processes[0]); - processes.resize(maxProcesses); - - if (!EnumProcesses(processes.data(), bytes, &bytes)) - { - return 1; - } - processes.resize(bytes / sizeof(processes[0])); - - std::array processesToTerminate = { - L"PowerToys.PowerLauncher.exe", - L"PowerToys.Settings.exe", - L"PowerToys.AdvancedPaste.exe", - L"PowerToys.Awake.exe", - L"PowerToys.FancyZones.exe", - L"PowerToys.FancyZonesEditor.exe", - L"PowerToys.FileLocksmithUI.exe", - L"PowerToys.MouseJumpUI.exe", - L"PowerToys.ColorPickerUI.exe", - L"PowerToys.AlwaysOnTop.exe", - L"PowerToys.RegistryPreview.exe", - L"PowerToys.Hosts.exe", - L"PowerToys.PowerRename.exe", - L"PowerToys.ImageResizer.exe", - L"PowerToys.GcodeThumbnailProvider.exe", - L"PowerToys.BgcodeThumbnailProvider.exe", - L"PowerToys.PdfThumbnailProvider.exe", - L"PowerToys.MonacoPreviewHandler.exe", - L"PowerToys.MarkdownPreviewHandler.exe", - L"PowerToys.StlThumbnailProvider.exe", - L"PowerToys.SvgThumbnailProvider.exe", - L"PowerToys.GcodePreviewHandler.exe", - L"PowerToys.BgcodePreviewHandler.exe", - L"PowerToys.QoiPreviewHandler.exe", - L"PowerToys.PdfPreviewHandler.exe", - L"PowerToys.QoiThumbnailProvider.exe", - L"PowerToys.SvgPreviewHandler.exe", - L"PowerToys.Peek.UI.exe", - L"PowerToys.MouseWithoutBorders.exe", - L"PowerToys.MouseWithoutBordersHelper.exe", - L"PowerToys.MouseWithoutBordersService.exe", - L"PowerToys.CropAndLock.exe", - L"PowerToys.EnvironmentVariables.exe", - L"PowerToys.WorkspacesSnapshotTool.exe", - L"PowerToys.WorkspacesLauncher.exe", - L"PowerToys.WorkspacesLauncherUI.exe", - L"PowerToys.WorkspacesEditor.exe", - L"PowerToys.WorkspacesWindowArranger.exe", - L"Microsoft.CmdPal.UI.exe", - L"PowerToys.ZoomIt.exe", - L"PowerToys.exe", - }; - - for (const auto procID : processes) - { - if (!procID) - { - continue; - } - wchar_t processName[MAX_PATH] = L""; - - HANDLE hProcess{OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, procID)}; - if (!hProcess) - { - continue; - } - HMODULE hMod; - DWORD cbNeeded; - - if (!EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) - { - CloseHandle(hProcess); - continue; - } - GetModuleBaseNameW(hProcess, hMod, processName, sizeof(processName) / sizeof(wchar_t)); - - for (const auto processToTerminate : processesToTerminate) - { - if (processName == processToTerminate) - { - const DWORD timeout = 500; - auto windowEnumerator = [](HWND hwnd, LPARAM procIDPtr) -> BOOL - { - auto targetProcID = *reinterpret_cast(procIDPtr); - DWORD windowProcID = 0; - GetWindowThreadProcessId(hwnd, &windowProcID); - if (windowProcID == targetProcID) - { - DWORD_PTR _{}; - SendMessageTimeoutA(hwnd, WM_CLOSE, 0, 0, SMTO_BLOCK, timeout, &_); - } - return TRUE; - }; - EnumWindows(windowEnumerator, reinterpret_cast(&procID)); - Sleep(timeout); - TerminateProcess(hProcess, 0); - break; - } - } - CloseHandle(hProcess); - } - - er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; - return WcaFinalize(er); -} - -void initSystemLogger() -{ - static std::once_flag initLoggerFlag; - std::call_once(initLoggerFlag, []() - { - WCHAR temp_path[MAX_PATH]; - auto ret = GetTempPath(MAX_PATH, temp_path); - - if (ret) - { - Logger::init("PowerToysMSI", std::wstring{ temp_path } + L"\\PowerToysMSIInstaller", L""); - } }); -} - -// DllMain - Initialize and cleanup WiX custom action utils. -extern "C" BOOL WINAPI DllMain(__in HINSTANCE hInst, __in ULONG ulReason, __in LPVOID) -{ - switch (ulReason) - { - case DLL_PROCESS_ATTACH: - WcaGlobalInitialize(hInst); - initSystemLogger(); - TraceLoggingRegister(g_hProvider); - DLL_HANDLE = hInst; - break; - - case DLL_PROCESS_DETACH: - TraceLoggingUnregister(g_hProvider); - WcaGlobalFinalize(); - break; - } - - return TRUE; -} diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def deleted file mode 100644 index 9467ca2204..0000000000 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ /dev/null @@ -1,34 +0,0 @@ -LIBRARY "PowerToysSetupCustomActions" - -EXPORTS - LaunchPowerToysCA - CheckGPOCA - CleanVideoConferenceRegistryCA - ApplyModulesRegistryChangeSetsCA - DetectPrevInstallPathCA - RemoveScheduledTasksCA - TelemetryLogInstallSuccessCA - TelemetryLogInstallCancelCA - TelemetryLogInstallFailCA - TelemetryLogUninstallSuccessCA - TelemetryLogUninstallCancelCA - TelemetryLogUninstallFailCA - TelemetryLogRepairCancelCA - TelemetryLogRepairFailCA - TerminateProcessesCA - InstallEmbeddedMSIXCA - InstallDSCModuleCA - InstallCmdPalPackageCA - UnApplyModulesRegistryChangeSetsCA - UnRegisterCmdPalPackageCA - UnRegisterContextMenuPackagesCA - UninstallEmbeddedMSIXCA - UninstallDSCModuleCA - UninstallServicesCA - UninstallCommandNotFoundModuleCA - UpgradeCommandNotFoundModuleCA - UnsetAdvancedPasteAPIKeyCA - CleanImageResizerRuntimeRegistryCA - CleanFileLocksmithRuntimeRegistryCA - CleanPowerRenameRuntimeRegistryCA - CleanNewPlusRuntimeRegistryCA diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj deleted file mode 100644 index 0974bddbf9..0000000000 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - {32f3882b-f2d6-4586-b5ed-11e39e522bd3} - Win32Proj - PowerToysSetupCustomActions - PowerToysSetupCustomActions - - - - DynamicLibrary - Unicode - v143 - - - DynamicLibrary - Unicode - true - v143 - - - - - - - - - - - - - - $(Platform)\$(Configuration)\MachineSetup\ - $(Platform)\$(Configuration)\UserSetup\ - $(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\MachineSetup\obj\ - $(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\UserSetup\obj\ - - false - true - - - true - - - false - ..\..\src\common\Telemetry;$(IncludePath) - - - - - call cmd /C "copy ""$(ProjectDir)DepsFilesLists.h"" ""$(ProjectDir)DepsFilesLists.h.bk""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\AdvancedPaste.wxs"" ""$(ProjectDir)..\PowerToysSetup\AdvancedPaste.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Awake.wxs"" ""$(ProjectDir)..\PowerToysSetup\Awake.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\BaseApplications.wxs"" ""$(ProjectDir)..\PowerToysSetup\BaseApplications.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\CmdPal.wxs"" ""$(ProjectDir)..\PowerToysSetup\CmdPal.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Core.wxs"" ""$(ProjectDir)..\PowerToysSetup\Core.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\EnvironmentVariables.wxs"" ""$(ProjectDir)..\PowerToysSetup\EnvironmentVariables.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileExplorerPreview.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileExplorerPreview.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileLocksmith.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileLocksmith.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Hosts.wxs"" ""$(ProjectDir)..\PowerToysSetup\Hosts.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ImageResizer.wxs"" ""$(ProjectDir)..\PowerToysSetup\ImageResizer.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\KeyboardManager.wxs"" ""$(ProjectDir)..\PowerToysSetup\KeyboardManager.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\MouseWithoutBorders.wxs"" ""$(ProjectDir)..\PowerToysSetup\MouseWithoutBorders.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\NewPlus.wxs"" ""$(ProjectDir)..\PowerToysSetup\NewPlus.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Peek.wxs"" ""$(ProjectDir)..\PowerToysSetup\Peek.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\PowerRename.wxs"" ""$(ProjectDir)..\PowerToysSetup\PowerRename.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Product.wxs"" ""$(ProjectDir)..\PowerToysSetup\Product.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\RegistryPreview.wxs"" ""$(ProjectDir)..\PowerToysSetup\RegistryPreview.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Resources.wxs"" ""$(ProjectDir)..\PowerToysSetup\Resources.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Run.wxs"" ""$(ProjectDir)..\PowerToysSetup\Run.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Settings.wxs"" ""$(ProjectDir)..\PowerToysSetup\Settings.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ShortcutGuide.wxs"" ""$(ProjectDir)..\PowerToysSetup\ShortcutGuide.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Tools.wxs"" ""$(ProjectDir)..\PowerToysSetup\Tools.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\WinAppSDK.wxs"" ""$(ProjectDir)..\PowerToysSetup\WinAppSDK.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\WinUI3Applications.wxs"" ""$(ProjectDir)..\PowerToysSetup\WinUI3Applications.wxs.bk"""" - call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Workspaces.wxs"" ""$(ProjectDir)..\PowerToysSetup\Workspaces.wxs.bk"""" - if not "$(NormalizedPerUserValue)" == "true" call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetup\generateAllFileComponents.ps1 -platform $(Platform) - if "$(NormalizedPerUserValue)" == "true" call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetup\generateAllFileComponents.ps1 -platform $(Platform) -installscopeperuser $(NormalizedPerUserValue) - - Backing up original files and populating .NET and WPF Runtime dependencies for WiX3 based installer - - - - false - false - - - - inc;..\..\src\;..\..\src\common\Telemetry;telemetry;$(WixSdkPath)VS2017\inc;%(AdditionalIncludeDirectories) - /await /Zc:twoPhase- /Wv:18 %(AdditionalOptions) - Level4 - ProgramDatabase - - - Userenv.lib;Wtsapi32.lib;WindowsApp.lib;Newdev.lib;Crypt32.lib;msi.lib;wcautil.lib;Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;Shlwapi.lib;%(AdditionalDependencies) - CustomAction.def - - - - - WIN64;%(PreprocessorDefinitions) - - - $(WixSdkPath)VS2017\lib\$(Platform);%(AdditionalLibraryDirectories) - - - - - Disabled - _DEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - - - true - Windows - HighestAvailable - - - - - MaxSpeed - true - NDEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions) - MultiThreaded - true - - - true - Windows - true - true - HighestAvailable - - - - - - Create - - - - - - - - - - - - - - - - - - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {8f021b46-362b-485c-bfba-ccf83e820cbd} - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj.filters b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj.filters deleted file mode 100644 index f4cf974dc0..0000000000 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj.filters +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - Telemetry - - - Telemetry - - - - - - - - - - - - {6e73ce5d-e715-4e7e-b796-c5d180b07ff2} - - - - - - \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/RcResource.h b/installer/PowerToysSetupCustomActions/RcResource.h deleted file mode 100644 index aabbb532bc..0000000000 --- a/installer/PowerToysSetupCustomActions/RcResource.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include - -class RcResource -{ -public: - const std::byte* _memory = nullptr; - size_t _size = 0; - - static inline std::optional create(int resource_id, const std::wstring_view resource_class, const HINSTANCE handle = nullptr) - { - const HRSRC resHandle = FindResourceW(handle, MAKEINTRESOURCEW(resource_id), resource_class.data()); - if (!resHandle) - { - return std::nullopt; - } - - const HGLOBAL memHandle = LoadResource(handle, resHandle); - if (!memHandle) - { - return std::nullopt; - } - - const size_t resSize = SizeofResource(handle, resHandle); - if (!resSize) - { - return std::nullopt; - } - - auto res = static_cast(LockResource(memHandle)); - if (!res) - { - return std::nullopt; - } - - return RcResource{ res, resSize }; - } - - inline bool saveAsFile(const std::filesystem::path destination) - { - std::fstream installerFile{ destination, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc }; - if (!installerFile.is_open()) - { - return false; - } - - installerFile.write(reinterpret_cast(_memory), _size); - return true; - } - -private: - RcResource() = delete; - RcResource(const std::byte* memory, size_t size) : - _memory{ memory }, _size{ size } - { - } -}; diff --git a/installer/PowerToysSetupCustomActions/Resource.rc b/installer/PowerToysSetupCustomActions/Resource.rc deleted file mode 100644 index c5f90c330d..0000000000 --- a/installer/PowerToysSetupCustomActions/Resource.rc +++ /dev/null @@ -1,96 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include -#include "resource.h" -#include "../../src/common/version/version.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -1 VERSIONINFO -FILEVERSION FILE_VERSION -PRODUCTVERSION PRODUCT_VERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG -FILEFLAGS VS_FF_DEBUG -#else -FILEFLAGS 0x0L -#endif -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset - BEGIN - VALUE "CompanyName", COMPANY_NAME - VALUE "FileDescription", FILE_DESCRIPTION - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "InternalName", INTERNAL_NAME - VALUE "LegalCopyright", COPYRIGHT_NOTE - VALUE "OriginalFilename", ORIGINAL_FILENAME - VALUE "ProductName", PRODUCT_NAME - VALUE "ProductVersion", PRODUCT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset - END -END - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) -LANGUAGE 25, 1 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - -// TODO: Use to activate embedded MSIX -//IDR_BIN_MSIX_HELLO_PACKAGE BIN "..\\..\..\\src\\modules\\HelloModule\\AppPackages\\HelloModule_1.0.0.0_x64_Test\\HelloModule_1.0.0.0_x64.msix" - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/installer/PowerToysSetupCustomActions/packages.config b/installer/PowerToysSetupCustomActions/packages.config deleted file mode 100644 index 09bfc449e2..0000000000 --- a/installer/PowerToysSetupCustomActions/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/pch.cpp b/installer/PowerToysSetupCustomActions/pch.cpp deleted file mode 100644 index 64b7eef6d6..0000000000 --- a/installer/PowerToysSetupCustomActions/pch.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// pch.cpp: source file corresponding to the pre-compiled header - -#include "pch.h" - -// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/installer/PowerToysSetupCustomActions/pch.h b/installer/PowerToysSetupCustomActions/pch.h deleted file mode 100644 index ebfdd0c258..0000000000 --- a/installer/PowerToysSetupCustomActions/pch.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#define DPSAPI_VERSION 1 -// Windows Header Files: -#include -#include -#include -#include -#include -#include - -// WiX Header Files: -#include - -#define SECURITY_WIN32 -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include diff --git a/installer/PowerToysSetupCustomActions/resource.h b/installer/PowerToysSetupCustomActions/resource.h deleted file mode 100644 index d31a222438..0000000000 --- a/installer/PowerToysSetupCustomActions/resource.h +++ /dev/null @@ -1,20 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Resource.rc - -#define FILE_DESCRIPTION "PowerToys Setup Custom Actions" -#define INTERNAL_NAME "PowerToysSetupCustomActions" -#define ORIGINAL_FILENAME "PowerToysSetupCustomActions.dll" - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 102 -#endif -#endif - -#define IDR_BIN_MSIX_HELLO_PACKAGE 101 diff --git a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp index 308b304591..0cfc3b1765 100644 --- a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp @@ -594,6 +594,216 @@ LExit: return WcaFinalize(er); } +UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR customActionData = nullptr; + std::wstring installFolderPath; + std::wstring installScope; + std::wstring msixPath; + std::wstring data; + size_t delimiterPos; + bool isMachineLevel = false; + + hr = WcaInitialize(hInstall, "InstallPackageIdentityMSIXCA"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = WcaGetProperty(L"CustomActionData", &customActionData); + ExitOnFailure(hr, "Failed to get CustomActionData property"); + + // Parse CustomActionData: "[INSTALLFOLDER];[InstallScope]" + data = customActionData; + delimiterPos = data.find(L';'); + installFolderPath = data.substr(0, delimiterPos); + installScope = data.substr(delimiterPos + 1); + + // Check if this is a machine-level installation + if (installScope == L"perMachine") + { + isMachineLevel = true; + } + + Logger::info(L"Installing PackageIdentity MSIX - perUser: {}", !isMachineLevel); + + // Construct path to PackageIdentity MSIX + msixPath = installFolderPath; + msixPath += L"PowerToysSparse.msix"; + + if (std::filesystem::exists(msixPath)) + { + using namespace winrt::Windows::Management::Deployment; + using namespace winrt::Windows::Foundation; + + try + { + + std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder) + Uri externalUri{ externalLocation }; // External location URI for sparse package content + Uri packageUri{ msixPath }; // The MSIX file URI + + PackageManager packageManager; + + if (isMachineLevel) + { + // Machine-level installation + + StagePackageOptions stageOptions; + stageOptions.ExternalLocationUri(externalUri); + + auto stageResult = packageManager.StagePackageByUriAsync(packageUri, stageOptions).get(); + + uint32_t stageErrorCode = static_cast(stageResult.ExtendedErrorCode()); + if (stageErrorCode == 0) + { + std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe"; + + try + { + auto provisionResult = packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName).get(); + uint32_t provisionErrorCode = static_cast(provisionResult.ExtendedErrorCode()); + + if (provisionErrorCode != 0) + { + Logger::error(L"Machine-level provisioning failed: 0x{:08X}", provisionErrorCode); + } + } + catch (const winrt::hresult_error& ex) + { + Logger::error(L"Provisioning exception: HRESULT 0x{:08X}", static_cast(ex.code())); + } + } + else + { + Logger::error(L"Package staging failed: 0x{:08X}", stageErrorCode); + } + } + else + { + AddPackageOptions addOptions; + addOptions.ExternalLocationUri(externalUri); + + auto addResult = packageManager.AddPackageByUriAsync(packageUri, addOptions).get(); + + if (!addResult.IsRegistered()) + { + uint32_t errorCode = static_cast(addResult.ExtendedErrorCode()); + Logger::error(L"Per-user installation failed: 0x{:08X}", errorCode); + } + } + } + catch (const std::exception& ex) + { + Logger::error(L"PackageIdentity MSIX installation failed - Exception: {}", + winrt::to_hstring(ex.what()).c_str()); + } + } + else + { + Logger::error(L"PackageIdentity MSIX not found: " + msixPath); + } + +LExit: + ReleaseStr(customActionData); + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall UninstallPackageIdentityMSIXCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + using namespace winrt::Windows::Management::Deployment; + using namespace winrt::Windows::Foundation; + + LPWSTR installScope = nullptr; + bool isMachineLevel = false; + + PackageManager pm; + + hr = WcaInitialize(hInstall, "UninstallPackageIdentityMSIXCA"); + ExitOnFailure(hr, "Failed to initialize"); + + // Check if this was a machine-level installation + hr = WcaGetProperty(L"InstallScope", &installScope); + if (SUCCEEDED(hr) && installScope && wcscmp(installScope, L"perMachine") == 0) + { + isMachineLevel = true; + } + + Logger::info(L"Uninstalling PackageIdentity MSIX - perUser: {}", !isMachineLevel); + + try + { + std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe"; + + if (isMachineLevel) + { + // Machine-level uninstallation: deprovision + remove for all users + + // First deprovision the package + try + { + auto deprovisionResult = pm.DeprovisionPackageForAllUsersAsync(packageFamilyName).get(); + if (deprovisionResult.IsRegistered()) + { + Logger::warn(L"Machine-level deprovisioning completed with warnings"); + } + } + catch (const winrt::hresult_error& ex) + { + Logger::warn(L"Machine-level deprovisioning failed: HRESULT 0x{:08X}", static_cast(ex.code())); + } + + // Then remove packages for all users + auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main); + for (const auto& package : packages) + { + try + { + auto machineResult = pm.RemovePackageAsync(package.Id().FullName(), RemovalOptions::RemoveForAllUsers).get(); + if (machineResult.IsRegistered()) + { + uint32_t errorCode = static_cast(machineResult.ExtendedErrorCode()); + Logger::error(L"Machine-level removal failed: 0x{:08X} - {}", errorCode, machineResult.ErrorText()); + } + } + catch (const winrt::hresult_error& ex) + { + Logger::error(L"Machine-level removal exception: HRESULT 0x{:08X}", static_cast(ex.code())); + } + } + } + else + { + // Per-user uninstallation: standard removal + + auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main); + for (const auto& package : packages) + { + auto userResult = pm.RemovePackageAsync(package.Id().FullName()).get(); + if (userResult.IsRegistered()) + { + uint32_t errorCode = static_cast(userResult.ExtendedErrorCode()); + Logger::error(L"Per-user removal failed: 0x{:08X} - {}", errorCode, userResult.ErrorText()); + } + } + } + } + catch (const std::exception& ex) + { + std::string errorMsg = "Failed to uninstall PackageIdentity MSIX: " + std::string(ex.what()); + Logger::error(errorMsg); + // Don't fail the entire uninstallation if PackageIdentity fails + Logger::warn(L"Continuing uninstallation despite PackageIdentity MSIX error"); + } + +LExit: + ReleaseStr(installScope); + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName) { SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); diff --git a/installer/PowerToysSetupCustomActionsVNext/CustomAction.def b/installer/PowerToysSetupCustomActionsVNext/CustomAction.def index 931a555953..4bad107f16 100644 --- a/installer/PowerToysSetupCustomActionsVNext/CustomAction.def +++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.def @@ -33,3 +33,5 @@ EXPORTS CleanPowerRenameRuntimeRegistryCA CleanNewPlusRuntimeRegistryCA SetBundleInstallLocationCA + InstallPackageIdentityMSIXCA + UninstallPackageIdentityMSIXCA diff --git a/installer/PowerToysSetupVNext/Common.wxi b/installer/PowerToysSetupVNext/Common.wxi index 4bb4f5a1dc..21855a7936 100644 --- a/installer/PowerToysSetupVNext/Common.wxi +++ b/installer/PowerToysSetupVNext/Common.wxi @@ -37,7 +37,7 @@ - + @@ -46,7 +46,7 @@ - + diff --git a/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj b/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj index 1a3a4a8cac..6b17b17030 100644 --- a/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj +++ b/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj @@ -3,7 +3,7 @@ false - Version=$(Version);InstallerSuffix=$(InstallerSuffix) + Version=$(Version) PowerToysVNextBootstrapper @@ -22,12 +22,11 @@ Release x64 arm64 - wix5 - PowerToysSetup-$(Version)-$(InstallerSuffix)-$(Platform) + PowerToysSetup-$(Version)-$(Platform) Bundle True - PowerToysSetup-$(Version)-$(InstallerSuffix)-$(Platform) - PowerToysUserSetup-$(Version)-$(InstallerSuffix)-$(Platform) + PowerToysSetup-$(Version)-$(Platform) + PowerToysUserSetup-$(Version)-$(Platform) $(Platform)\$(Configuration)\MachineSetup $(Platform)\$(Configuration)\UserSetup $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\MachineSetup diff --git a/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj b/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj index 5341f66768..afce3d396d 100644 --- a/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj +++ b/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj @@ -4,7 +4,7 @@ false - Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion);InstallerSuffix=$(InstallerSuffix) @@ -18,7 +18,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil - Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion);InstallerSuffix=$(InstallerSuffix) + Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion) IF NOT DEFINED IsPipeline ( call "$([MSBuild]::GetVsInstallRoot())\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=amd64 -winsdk=10.0.19041.0 -vcvars_ver=$(VCToolsVersion) SET PTRoot=$(SolutionDir)\.. @@ -80,9 +80,8 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil 3.10 {b6e94700-df38-41f6-a3fd-18b69674ab1e} 2.0 - wix5 - PowerToysSetup-$(Version)-$(InstallerSuffix)-$(Platform) - PowerToysUserSetup-$(Version)-$(InstallerSuffix)-$(Platform) + PowerToysSetup-$(Version)-$(Platform) + PowerToysUserSetup-$(Version)-$(Platform) Package True diff --git a/installer/PowerToysSetupVNext/Product.wxs b/installer/PowerToysSetupVNext/Product.wxs index 2505557d77..556fddc7f4 100644 --- a/installer/PowerToysSetupVNext/Product.wxs +++ b/installer/PowerToysSetupVNext/Product.wxs @@ -112,6 +112,7 @@ + @@ -123,6 +124,7 @@ + @@ -144,6 +146,7 @@ + + + + + + PowerToys.SparseApp + PowerToys + Images\StoreLogo.png + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PackageIdentity/BuildSparsePackage.cmd b/src/PackageIdentity/BuildSparsePackage.cmd new file mode 100644 index 0000000000..71a4a6a77c --- /dev/null +++ b/src/PackageIdentity/BuildSparsePackage.cmd @@ -0,0 +1,6 @@ +@echo off +REM Wrapper to invoke PowerToys sparse package build script. +REM Pass through all arguments (e.g. Platform=arm64 Configuration=Debug -Clean) + +powershell -ExecutionPolicy Bypass -NoLogo -NoProfile -File "%~dp0\BuildSparsePackage.ps1" %* +exit /b %ERRORLEVEL% diff --git a/src/PackageIdentity/BuildSparsePackage.ps1 b/src/PackageIdentity/BuildSparsePackage.ps1 new file mode 100644 index 0000000000..1e341c24f5 --- /dev/null +++ b/src/PackageIdentity/BuildSparsePackage.ps1 @@ -0,0 +1,422 @@ +#Requires -Version 5.1 + +[CmdletBinding()] +Param( + [Parameter(Mandatory=$false)] + [ValidateSet("arm64", "x64")] + [string]$Platform = "x64", + + [Parameter(Mandatory=$false)] + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Release", + + [switch]$Clean, + [switch]$ForceCert, + [switch]$NoSign, + [switch]$CIBuild +) + +# PowerToys sparse packaging helper. +# Generates a sparse MSIX (no payload) that grants package identity to selected Win32 components. +# Multiple applications (PowerOCR, Settings UI, etc.) can share this single sparse identity. + +$ErrorActionPreference = 'Stop' + +$isCIBuild = $false +if ($CIBuild.IsPresent) { + $isCIBuild = $true +} elseif ($env:CIBuild) { + $isCIBuild = $env:CIBuild -ieq 'true' +} + +$currentPublisherHint = $script:Config.CertSubject + +# Configuration constants - centralized management +$script:Config = @{ + IdentityName = "Microsoft.PowerToys.SparseApp" + SparseMsixName = "PowerToysSparse.msix" + CertPrefix = "PowerToysSparse" + CertSubject = 'CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US' + CertValidMonths = 12 +} + +#region Helper Functions + +function Find-WindowsSDKTool { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$ToolName, + + [Parameter(Mandatory=$false)] + [string]$Architecture = "x64" + ) + + # Simple fallback: check common Windows SDK locations + $commonPaths = @( + "${env:ProgramFiles}\Windows Kits\10\bin\*\$Architecture\$ToolName", + "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\$Architecture\$ToolName", + "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x86\$ToolName" # SignTool fallback + ) + + foreach ($pattern in $commonPaths) { + $found = Get-ChildItem $pattern -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | + Select-Object -First 1 + if ($found) { + Write-BuildLog "Found $ToolName at: $($found.FullName)" -Level Info + return $found.FullName + } + } + + throw "$ToolName not found. Please ensure Windows SDK is installed." +} + +function Test-CertificateValidity { + param([string]$ThumbprintFile) + + if (-not (Test-Path $ThumbprintFile)) { return $false } + + try { + $thumb = (Get-Content $ThumbprintFile -Raw).Trim() + if (-not $thumb) { return $false } + $cert = Get-Item "cert:\CurrentUser\My\$thumb" -ErrorAction Stop + return $cert.HasPrivateKey -and $cert.NotAfter -gt (Get-Date) + } catch { + return $false + } +} + +function Write-BuildLog { + param([string]$Message, [string]$Level = "Info") + + $colors = @{ Error = "Red"; Warning = "Yellow"; Success = "Green"; Info = "Cyan" } + $color = if ($colors.ContainsKey($Level)) { $colors[$Level] } else { "White" } + + Write-Host "[$(Get-Date -f 'HH:mm:ss')] $Message" -ForegroundColor $color +} + +function Stop-FileProcesses { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$FilePath + ) + + # This function is kept for compatibility but simplified since + # the staging directory approach resolves the file lock issues + Write-Verbose "File process check for: $FilePath" +} + +#endregion + +# Environment diagnostics for troubleshooting +Write-BuildLog "Starting PackageIdentity build process..." -Level Info +Write-BuildLog "PowerShell Version: $($PSVersionTable.PSVersion)" -Level Info +try { + $execPolicy = Get-ExecutionPolicy + Write-BuildLog "Execution Policy: $execPolicy" -Level Info +} catch { + Write-BuildLog "Execution Policy: Unable to determine (MSBuild environment)" -Level Info +} +Write-BuildLog "Current User: $env:USERNAME" -Level Info +Write-BuildLog "Build Platform: $Platform, Configuration: $Configuration" -Level Info + +# Check for Visual Studio environment +if ($env:VSINSTALLDIR) { + Write-BuildLog "Running in Visual Studio environment: $env:VSINSTALLDIR" -Level Info +} + +# Ensure certificate provider is available +try { + # Force load certificate provider for MSBuild environment + if (-not (Get-PSProvider -PSProvider Certificate -ErrorAction SilentlyContinue)) { + Write-BuildLog "Loading certificate provider..." -Level Warning + Import-Module Microsoft.PowerShell.Security -Force + } + if (-not (Test-Path 'Cert:\CurrentUser')) { + Write-BuildLog "Certificate drive not available, attempting to initialize..." -Level Warning + Import-Module PKI -ErrorAction SilentlyContinue + # Try to access the certificate store to force initialization + Get-ChildItem "Cert:\CurrentUser\My" -ErrorAction SilentlyContinue | Out-Null + } +} catch { + Write-BuildLog ("Note: Certificate provider setup may need manual configuration: {0}" -f $_) -Level Warning +} + +# Project root folder (now set to current script folder for local builds) +$ProjectRoot = $PSScriptRoot +$UserFolder = Join-Path $ProjectRoot '.user' +if (-not (Test-Path $UserFolder)) { New-Item -ItemType Directory -Path $UserFolder | Out-Null } + +# Certificate file paths using configuration +$prefix = $script:Config.CertPrefix +$CertThumbFile, $CertCerFile = @('.thumbprint', '.cer') | + ForEach-Object { Join-Path $UserFolder "$prefix.certificate.sample$_" } + +# Clean option: remove bin/obj and uninstall existing sparse package if present +if ($Clean) { + Write-BuildLog "Cleaning build artifacts..." -Level Info + 'bin','obj' | ForEach-Object { + $target = Join-Path $ProjectRoot $_ + if (Test-Path $target) { Remove-Item $target -Recurse -Force } + } + Write-BuildLog "Attempting to remove existing sparse package (best effort)" -Level Info + try { Get-AppxPackage -Name $script:Config.IdentityName | Remove-AppxPackage } catch {} +} + +# Force certificate regeneration if requested +if ($ForceCert -and (Test-Path $UserFolder)) { + Write-BuildLog "ForceCert specified: removing existing certificate artifacts..." -Level Warning + Remove-Item $UserFolder -Recurse -Force + New-Item -ItemType Directory -Path $UserFolder | Out-Null +} + +# Ensure dev cert (development only; not for production use) - skip if NoSign specified +$needNewCert = -not $NoSign -and (-not (Test-Path $CertThumbFile) -or $ForceCert -or -not (Test-CertificateValidity -ThumbprintFile $CertThumbFile)) + +if ($needNewCert) { + Write-BuildLog "Generating development certificate (prefix=$($script:Config.CertPrefix))..." -Level Info + + # Clear stale files in the certificate cache + if (Test-Path $UserFolder) { + Get-ChildItem -Path $UserFolder | ForEach-Object { + if ($_.PSIsContainer) { + Remove-Item $_.FullName -Recurse -Force + } else { + Remove-Item $_.FullName -Force + } + } + } + if (-not (Test-Path $UserFolder)) { + New-Item -ItemType Directory -Path $UserFolder | Out-Null + } + + $now = Get-Date + $expiration = $now.AddMonths($script:Config.CertValidMonths) + # Subject MUST match inside AppxManifest.xml + $friendlyName = "PowerToys Dev Sparse Cert Create=$now" + $keyFriendly = "PowerToys Dev Sparse Key Create=$now" + + $certStore = 'cert:\CurrentUser\My' + $ekuOid = '2.5.29.37' + $ekuValue = '1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13' + $eku = "$ekuOid={text}$ekuValue" + + $cert = New-SelfSignedCertificate -CertStoreLocation $certStore ` + -NotAfter $expiration ` + -Subject $script:Config.CertSubject ` + -FriendlyName $friendlyName ` + -KeyFriendlyName $keyFriendly ` + -KeyDescription $keyFriendly ` + -TextExtension $eku + + # Export certificate files + Set-Content -Path $CertThumbFile -Value $cert.Thumbprint -Force + Export-Certificate -Cert $cert -FilePath $CertCerFile -Force | Out-Null +} + +# Determine output directory - using PowerToys standard structure +# Navigate to PowerToys root (two levels up from src/PackageIdentity) +$PowerToysRoot = Split-Path (Split-Path $ProjectRoot -Parent) -Parent +$outDir = Join-Path $PowerToysRoot "$Platform\$Configuration" + +if (-not (Test-Path $outDir)) { + Write-BuildLog "Creating output directory: $outDir" -Level Info + New-Item -ItemType Directory -Path $outDir -Force | Out-Null +} + +# PackageIdentity folder (this script location) containing the sparse manifest and assets +$sparseDir = $PSScriptRoot +$manifestPath = Join-Path $sparseDir 'AppxManifest.xml' +if (-not (Test-Path $manifestPath)) { throw "Missing AppxManifest.xml in PackageIdentity folder: $manifestPath" } + +$versionPropsPath = Join-Path $PowerToysRoot 'src\Version.props' +$targetManifestVersion = $null +$versionCandidate = $null +if (Test-Path $versionPropsPath) { + try { + [xml]$propsXml = Get-Content -Path $versionPropsPath -Raw + $versionCandidate = $propsXml.Project.PropertyGroup.Version + } catch { + Write-BuildLog ("Unable to read version from {0}: {1}" -f $versionPropsPath, $_) -Level Warning + } +} else { + Write-BuildLog "Version.props not found at $versionPropsPath; manifest version will remain unchanged." -Level Warning +} + +if ($versionCandidate) { + $targetManifestVersion = $versionCandidate.Trim() + if (($targetManifestVersion -split '\.').Count -lt 4) { + $targetManifestVersion = "$targetManifestVersion.0" + } + Write-BuildLog "Using sparse package version from Version.props: $targetManifestVersion" -Level Info +} else { + Write-BuildLog "No version value provided; manifest version will remain unchanged." -Level Info +} + +# Find MakeAppx.exe from Windows SDK +try { + $hostSdkArchitecture = if ([System.Environment]::Is64BitProcess) { 'x64' } else { 'x86' } + $makeAppxPath = Find-WindowsSDKTool -ToolName "makeappx.exe" -Architecture $hostSdkArchitecture +} catch { + Write-Error "MakeAppx.exe not found. Please ensure Windows SDK is installed." + exit 1 +} + +# Pack sparse MSIX from PackageIdentity folder +$msixPath = Join-Path $outDir $script:Config.SparseMsixName + +# Clean up existing MSIX file +if (Test-Path $msixPath) { + Write-BuildLog "Removing existing MSIX file..." -Level Info + try { + Remove-Item $msixPath -Force -ErrorAction Stop + Write-BuildLog "Successfully removed existing MSIX file" -Level Success + } catch { + Write-BuildLog ("Warning: Could not remove existing MSIX file: {0}" -f $_) -Level Warning + } +} + +# Create a clean staging directory to avoid file lock issues +$stagingDir = Join-Path $outDir "staging" +if (Test-Path $stagingDir) { + Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue +} +New-Item -ItemType Directory -Path $stagingDir -Force | Out-Null + +try { + Write-BuildLog "Creating clean staging directory for packaging..." -Level Info + + # Copy only essential files to staging directory to avoid file locks + $essentialFiles = @( + "AppxManifest.xml" + "Images\*" + ) + + foreach ($filePattern in $essentialFiles) { + $sourcePath = Join-Path $sparseDir $filePattern + $relativePath = $filePattern + + if ($filePattern.Contains('\')) { + $targetDir = Join-Path $stagingDir (Split-Path $relativePath -Parent) + if (-not (Test-Path $targetDir)) { + New-Item -ItemType Directory -Path $targetDir -Force | Out-Null + } + } + + if ($filePattern.EndsWith('\*')) { + # Copy directory contents + $sourceDir = $sourcePath.TrimEnd('\*') + $targetDir = Join-Path $stagingDir (Split-Path $relativePath.TrimEnd('\*') -Parent) + if (Test-Path $sourceDir) { + Copy-Item -Path "$sourceDir\*" -Destination $targetDir -Force -ErrorAction SilentlyContinue + } + } else { + # Copy single file + $targetPath = Join-Path $stagingDir $relativePath + if (Test-Path $sourcePath) { + Copy-Item -Path $sourcePath -Destination $targetPath -Force -ErrorAction SilentlyContinue + } + } + } + + # Ensure publisher matches the dev certificate for local builds + $manifestStagingPath = Join-Path $stagingDir 'AppxManifest.xml' + $shouldUseDevPublisher = -not $isCIBuild + if (Test-Path $manifestStagingPath) { + try { + [xml]$manifestXml = Get-Content -Path $manifestStagingPath -Raw + $identityNode = $manifestXml.Package.Identity + $manifestChanged = $false + if ($identityNode) { + $currentPublisherHint = $identityNode.Publisher + } + + if ($identityNode) { + if ($targetManifestVersion -and $identityNode.Version -ne $targetManifestVersion) { + Write-BuildLog "Updating manifest version to $targetManifestVersion" -Level Info + $identityNode.SetAttribute('Version', $targetManifestVersion) + $manifestChanged = $true + } + + if ($shouldUseDevPublisher -and $identityNode.Publisher -ne $script:Config.CertSubject) { + Write-BuildLog "Updating manifest publisher for local build" -Level Warning + $identityNode.SetAttribute('Publisher', $script:Config.CertSubject) + $manifestChanged = $true + } + $currentPublisherHint = $identityNode.Publisher + } + + if ($manifestChanged) { + $manifestXml.Save($manifestStagingPath) + } + } catch { + Write-BuildLog ("Unable to adjust manifest metadata: {0}" -f $_) -Level Warning + } + } + + Write-BuildLog "Staging directory prepared with essential files only" -Level Success + + # Pack MSIX using staging directory + Write-BuildLog "Packing sparse MSIX ($($script:Config.SparseMsixName)) from staging -> $msixPath" -Level Info + + & $makeAppxPath pack /d $stagingDir /p $msixPath /nv /o + + if ($LASTEXITCODE -eq 0 -and (Test-Path $msixPath)) { + Write-BuildLog "MSIX packaging completed successfully" -Level Success + } else { + Write-BuildLog "MakeAppx failed with exit code $LASTEXITCODE" -Level Error + exit 1 + } +} finally { + # Clean up staging directory + if (Test-Path $stagingDir) { + try { + Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue + Write-BuildLog "Cleaned up staging directory" -Level Info + } catch { + Write-BuildLog ("Warning: Could not clean up staging directory: {0}" -f $_) -Level Warning + } + } +} + +# Sign package (skip if NoSign specified for CI scenarios) +if ($NoSign) { + Write-BuildLog "Skipping signing (NoSign specified for CI build)" -Level Warning +} else { + # Use certificate thumbprint for signing (safer, no password) + $certThumbprint = (Get-Content -Path $CertThumbFile -Raw).Trim() + try { + $signToolPath = Find-WindowsSDKTool -ToolName "signtool.exe" + } catch { + Write-Error "SignTool.exe not found. Please ensure Windows SDK is installed." + exit 1 + } + Write-BuildLog "Signing sparse MSIX using cert thumbprint $certThumbprint..." -Level Info + & $signToolPath sign /fd SHA256 /sha1 $certThumbprint $msixPath + if ($LASTEXITCODE -ne 0) { + Write-Warning "SignTool failed (exit $LASTEXITCODE). Ensure the certificate is in CurrentUser\\My and try -ForceCert if needed." + exit $LASTEXITCODE + } +} + +$publisherHintFile = Join-Path $UserFolder "$($script:Config.CertPrefix).publisher.txt" +try { + Set-Content -Path $publisherHintFile -Value $currentPublisherHint -Force -NoNewline +} catch { + Write-BuildLog ("Unable to write publisher hint: {0}" -f $_) -Level Warning +} + +Write-BuildLog "`nPackage created: $msixPath" -Level Success + +if ($NoSign) { + Write-BuildLog "UNSIGNED package created for CI build. Sign before deployment." -Level Warning +} else { + Write-BuildLog "Install the dev certificate (once): $CertCerFile" -Level Info + Write-BuildLog "Identity Name: $($script:Config.IdentityName)" -Level Info +} + +Write-BuildLog "Register sparse package:" -Level Info +Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning +Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning diff --git a/src/PackageIdentity/Check-ProcessIdentity.ps1 b/src/PackageIdentity/Check-ProcessIdentity.ps1 new file mode 100644 index 0000000000..767afe542f --- /dev/null +++ b/src/PackageIdentity/Check-ProcessIdentity.ps1 @@ -0,0 +1,43 @@ +<# +.SYNOPSIS + Determine whether a given process (by PID) runs with an MSIX/UWP package identity. +.DESCRIPTION + Calls the Windows API GetPackageFullName to check if the target process executes under an MSIX/Sparse App/UWP package identity. + Returns the package full name when identity is present, or "No package identity" otherwise. +.PARAMETER ProcessId + The process ID to inspect. +.EXAMPLE + .\Check-ProcessIdentity.ps1 -pid 12345 +#> +param( + [Parameter(Mandatory=$true)] + [int]$ProcessId +) + +Add-Type -TypeDefinition @' +using System; +using System.Text; +using System.Runtime.InteropServices; +public class P { + [DllImport("kernel32.dll", SetLastError=true)] + public static extern IntPtr OpenProcess(uint a, bool b, int p); + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool CloseHandle(IntPtr h); + [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] + public static extern int GetPackageFullName(IntPtr h, ref int l, StringBuilder b); + public static string G(int pid) { + IntPtr h = OpenProcess(0x1000, false, pid); + if (h == IntPtr.Zero) return "Failed to open process"; + int len = 0; + GetPackageFullName(h, ref len, null); + if (len == 0) { CloseHandle(h); return "No package identity"; } + var sb = new StringBuilder(len); + int r = GetPackageFullName(h, ref len, sb); + CloseHandle(h); + return r == 0 ? sb.ToString() : "Error:" + r; + } +} +'@ + +$result = [P]::G($ProcessId) +Write-Output $result diff --git a/src/PackageIdentity/Images/Square150x150Logo.png b/src/PackageIdentity/Images/Square150x150Logo.png new file mode 100644 index 0000000000..01a45755d7 Binary files /dev/null and b/src/PackageIdentity/Images/Square150x150Logo.png differ diff --git a/src/PackageIdentity/Images/Square44x44Logo.png b/src/PackageIdentity/Images/Square44x44Logo.png new file mode 100644 index 0000000000..01a45755d7 Binary files /dev/null and b/src/PackageIdentity/Images/Square44x44Logo.png differ diff --git a/src/PackageIdentity/Images/StoreLogo.png b/src/PackageIdentity/Images/StoreLogo.png new file mode 100644 index 0000000000..01a45755d7 Binary files /dev/null and b/src/PackageIdentity/Images/StoreLogo.png differ diff --git a/src/PackageIdentity/PackageIdentity.vcxproj b/src/PackageIdentity/PackageIdentity.vcxproj new file mode 100644 index 0000000000..b8bd2dc1f2 --- /dev/null +++ b/src/PackageIdentity/PackageIdentity.vcxproj @@ -0,0 +1,120 @@ + + + + + + true + true + + + + + + + -NoSign + + -CIBuild + + + + + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + 15.0 + Win32Proj + {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D} + PackageIdentity + PackageIdentity + false + + + + + + Utility + true + v143 + + + + Utility + false + v143 + true + + + + Utility + true + v143 + + + + Utility + false + v143 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + Images + + + Images + + + Images + + + + + + + + + \ No newline at end of file diff --git a/src/PackageIdentity/PackageIdentity.vcxproj.filters b/src/PackageIdentity/PackageIdentity.vcxproj.filters new file mode 100644 index 0000000000..608c80f2b9 --- /dev/null +++ b/src/PackageIdentity/PackageIdentity.vcxproj.filters @@ -0,0 +1,25 @@ + + + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + png;jpg;jpeg;gif;bmp;ico + + + + + + + + + + Images + + + Images + + + Images + + + \ No newline at end of file diff --git a/src/PackageIdentity/readme.md b/src/PackageIdentity/readme.md new file mode 100644 index 0000000000..2af2bbb26d --- /dev/null +++ b/src/PackageIdentity/readme.md @@ -0,0 +1,90 @@ +# PowerToys sparse package identity + +This document describes how to build, sign, register, and consume the shared sparse MSIX package that grants package identity to select Win32 components of PowerToys. + +## Package overview + +The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer). + +> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`). + +## Building the sparse package locally + +Two options are available: + +- Build the utility project from Visual Studio: `PackageIdentity.vcxproj` defines a `GenerateSparsePackage` target that runs before `PrepareForBuild` and invokes the helper script automatically. +- Invoke the helper script directly from PowerShell: + +```powershell +$repoRoot = "C:/git/PowerToys" +pwsh "$repoRoot/src/PackageIdentity/BuildSparsePackage.ps1" -Platform x64 -Configuration Release +``` + +Supported switches: + +- `-Clean` removes previous `bin`/`obj` outputs and uninstalls existing installation. +- `-ForceCert` regenerates the local dev certificate (.pfx/.cer/.pwd/.thumbprint) under `src/PackageIdentity/.user`. +- `-NoSign` skips signing. The MSIX still builds but must be signed before deployment. +- `-CIBuild` (or setting `$env:CIBuild = 'true'`) keeps the manifest publisher intact and skips the local cert substitution. + +The script determines the proper `makeappx.exe` for the host build machine (x64 on typical developer boxes) and creates `PowerToysSparse.msix` in `{repo}\\`. + +> After packaging finishes, the helper also emits `src/PackageIdentity/.user/PowerToysSparse.publisher.txt`. This file mirrors the publisher string Windows will see once the sparse package is registered, which downstream projects can read to stay in sync when generating their own manifests. + +## Local signing basics + +When `-NoSign` is not used the script generates (or reuses) a development certificate and signs the package via `signtool.exe`: + +1. Artifacts are stored in `src/PackageIdentity/.user/PowerToysSparse.certificate.sample.*` (`.cer` and `.thumbprint`). +2. Install the `.cer` into `CurrentUser` → `TrustedPeople` (and `TrustedRoot`, if necessary) so Windows trusts the signature: + + ```powershell + $repoRoot = "C:/git/PowerToys" + Import-Certificate -FilePath "$repoRoot/src/PackageIdentity/.user/PowerToysSparse.certificate.sample.cer" -CertStoreLocation Cert:\CurrentUser\TrustedPeople + ``` + +3. The private key stays in the current user's personal certificate store. + +## Registering or unregistering the package + +After `PowerToysSparse.msix` is generated: + +```powershell +# First time registration +$repoRoot = "C:/git/PowerToys" +$outputRoot = Join-Path $repoRoot "x64/Release" +Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot + +# Re-register after manifest tweaks only +Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown + +# Remove the sparse identity +Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage +``` + +`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes. + +## CI-specific guidance + +- Pass `-CIBuild` to `BuildSparsePackage.ps1` (or build with `msbuild PackageIdentity.vcxproj /p:CIBuild=true`). This prevents the script from rewriting the manifest publisher to the local dev certificate subject. +- The project automatically adds `-NoSign` only when `$(CIBuild)` is `true`. Local Debug and Release builds are signed with the development certificate. +- Make sure the agent trusts whichever certificate signs the package. If the package remains unsigned (`-NoSign`) it cannot be installed on test machines until it is signed. + +## Consuming the identity from other components + +1. Add a new `` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root. +2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it. +3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the ``, and a `publisher` that matches the sparse package. Keep the manifest’s publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`. +4. Register or re-register the sparse package so Windows learns about the new application Id. +5. To launch the Win32 surface with identity, use the `shell:AppsFolder` activation form (for example: `shell:AppsFolder\Microsoft.PowerToys.SparseApp_!PowerToys.MyModuleUI`) or activate it via `IApplicationActivationManager::ActivateApplication` using the same AppUserModelID. + + - For locally built packages, resolve the `` with `Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Select-Object -ExpandProperty PackageFamilyName`. + - Store-distributed builds use `Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe`. Local developer builds created by this script typically use a different family name derived from the dev certificate. + +6. Context menu handlers or other launchers should fall back to the unpackaged executable path for environments where the sparse package is not present. + +## Troubleshooting tips + +- `Program 'makeappx.exe' failed to run`: make sure you are running an x64 PowerShell host. The script now chooses the appropriate makeappx automatically; update your repo if the log still points to an ARM64 binary. +- `HRESULT 0x800B0109 (trust failure)`: install the development certificate into both `TrustedPeople` and `TrustedRoot` stores for the current user. +- Stale registration: remove the package with `Remove-AppxPackage` and re-run the script with `-Clean` to rebuild from scratch. diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index df4ac87581..ad4c417b31 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -281,21 +281,7 @@ namespace Awake.Core TimeSpan remainingTime = expireAt - DateTimeOffset.Now; Observable.Timer(remainingTime).Subscribe( - _ => - { - Logger.LogInfo("Completed expirable keep-awake."); - CancelExistingThread(); - - if (IsUsingPowerToysConfig) - { - SetPassiveKeepAwake(); - } - else - { - Logger.LogInfo("Exiting after expirable keep awake."); - CompleteExit(Environment.ExitCode); - } - }, + _ => HandleTimerCompletion("expirable"), _tokenSource.Token); } @@ -348,49 +334,46 @@ namespace Awake.Core SetModeShellIcon(); - ulong desiredDuration = (ulong)seconds * 1000; - ulong targetDuration = Math.Min(desiredDuration, uint.MaxValue - 1) / 1000; + var targetExpiryTime = DateTimeOffset.Now.AddSeconds(seconds); - if (desiredDuration > uint.MaxValue) - { - Logger.LogInfo($"The desired interval of {seconds} seconds ({desiredDuration}ms) exceeds the limit. Defaulting to maximum possible value: {targetDuration} seconds. Read more about existing limits in the official documentation: https://aka.ms/powertoys/awake"); - } - - IObservable timerObservable = Observable.Timer(TimeSpan.FromSeconds(targetDuration)); - IObservable intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable); - IObservable combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1); - - combinedObservable.Subscribe( - elapsedSeconds => - { - TimeRemaining = (uint)targetDuration - (uint)elapsedSeconds; - if (TimeRemaining >= 0) + Observable.Interval(TimeSpan.FromSeconds(1)) + .Select(_ => targetExpiryTime - DateTimeOffset.Now) + .TakeWhile(remaining => remaining.TotalSeconds > 0) + .Subscribe( + remainingTimeSpan => { + TimeRemaining = (uint)remainingTimeSpan.TotalSeconds; + TrayHelper.SetShellIcon( TrayHelper.WindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{TimeSpan.FromSeconds(TimeRemaining).ToHumanReadableString()}]", + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{remainingTimeSpan.ToHumanReadableString()}]", TrayHelper.TimedIcon, TrayIconAction.Update); - } - }, - () => - { - Logger.LogInfo("Completed timed thread."); - CancelExistingThread(); + }, + _ => HandleTimerCompletion("timed"), + _tokenSource.Token); + } - if (IsUsingPowerToysConfig) - { - // If we're using PowerToys settings, we need to make sure that - // we just switch over the Passive Keep-Awake. - SetPassiveKeepAwake(); - } - else - { - Logger.LogInfo("Exiting after timed keep-awake."); - CompleteExit(Environment.ExitCode); - } - }, - _tokenSource.Token); + /// + /// Handles the common logic that should execute when a keep-awake timer completes. Resets + /// the application state to Passive if configured; otherwise it exits. + /// + private static void HandleTimerCompletion(string timerType) + { + Logger.LogInfo($"Completed {timerType} keep-awake."); + CancelExistingThread(); + + if (IsUsingPowerToysConfig) + { + // If running under PowerToys settings, just revert to the default Passive state. + SetPassiveKeepAwake(); + } + else + { + // If running as a standalone process, exit cleanly. + Logger.LogInfo($"Exiting after {timerType} keep-awake."); + CompleteExit(Environment.ExitCode); + } } /// diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/PathHelper.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/PathHelper.cs new file mode 100644 index 0000000000..75cfcac444 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/PathHelper.cs @@ -0,0 +1,153 @@ +// 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.Runtime.CompilerServices; +using Windows.Win32; +using Windows.Win32.Storage.FileSystem; + +namespace Microsoft.CmdPal.Core.Common.Helpers; + +public static class PathHelper +{ + public static bool Exists(string path, out bool isDirectory) + { + isDirectory = false; + if (string.IsNullOrEmpty(path)) + { + return false; + } + + string? fullPath; + try + { + fullPath = Path.GetFullPath(path); + } + catch (Exception ex) when (ex is ArgumentException or IOException or UnauthorizedAccessException) + { + return false; + } + + var result = ExistsCore(fullPath, out isDirectory); + if (result && IsDirectorySeparator(fullPath[^1])) + { + // Some sys-calls remove all trailing slashes and may give false positives for existing files. + // We want to make sure that if the path ends in a trailing slash, it's truly a directory. + return isDirectory; + } + + return result; + } + + /// + /// Normalize potential local/UNC file path text input: trim whitespace and surrounding quotes. + /// Windows file paths cannot contain quotes, but user input can include them. + /// + public static string Unquote(string? text) + { + return string.IsNullOrWhiteSpace(text) ? (text ?? string.Empty) : text.Trim().Trim('"'); + } + + /// + /// Quick heuristic to determine if the string looks like a Windows file path (UNC or drive-letter based). + /// + public static bool LooksLikeFilePath(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } + + // UNC path + if (path.StartsWith(@"\\", StringComparison.Ordinal)) + { + // Win32 File Namespaces \\?\ + if (path.StartsWith(@"\\?\", StringComparison.Ordinal)) + { + return IsSlow(path[4..]); + } + + // Basic UNC path validation: \\server\share or \\server\share\path + var parts = path[2..].Split('\\', StringSplitOptions.RemoveEmptyEntries); + + return parts.Length >= 2; // At minimum: server and share + } + + // Drive letter path (e.g., C:\ or C:) + return path.Length >= 2 && char.IsLetter(path[0]) && path[1] == ':'; + } + + /// + /// Validates path syntax without performing any I/O by using Path.GetFullPath. + /// + public static bool HasValidPathSyntax(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } + + try + { + _ = Path.GetFullPath(path); + return true; + } + catch + { + return false; + } + } + + /// + /// Checks if a string represents a valid Windows file path (local or network) + /// using fast syntax validation only. Reuses LooksLikeFilePath and HasValidPathSyntax. + /// + public static bool IsValidFilePath(string? path) + { + return LooksLikeFilePath(path) && HasValidPathSyntax(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDirectorySeparator(char c) + { + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + } + + private static bool ExistsCore(string fullPath, out bool isDirectory) + { + var attributes = PInvoke.GetFileAttributes(fullPath); + var result = attributes != PInvoke.INVALID_FILE_ATTRIBUTES; + isDirectory = result && (attributes & (uint)FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DIRECTORY) != 0; + return result; + } + + public static bool IsSlow(string path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + try + { + var root = Path.GetPathRoot(path); + if (!string.IsNullOrEmpty(root)) + { + if (root.Length > 2 && char.IsLetter(root[0]) && root[1] == ':') + { + return new DriveInfo(root).DriveType is not (DriveType.Fixed or DriveType.Ram); + } + else if (root.StartsWith(@"\\", StringComparison.Ordinal)) + { + return !root.StartsWith(@"\\?\", StringComparison.Ordinal) || IsSlow(root[4..]); + } + } + + return false; + } + catch + { + return false; + } + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/NativeMethods.txt b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/NativeMethods.txt index 61e89b68c4..03318381a6 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/NativeMethods.txt +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/NativeMethods.txt @@ -12,4 +12,8 @@ MonitorFromWindow SHOW_WINDOW_CMD ShellExecuteEx -SEE_MASK_INVOKEIDLIST \ No newline at end of file +SEE_MASK_INVOKEIDLIST + +GetFileAttributes +FILE_FLAGS_AND_ATTRIBUTES +INVALID_FILE_ATTRIBUTES \ No newline at end of file diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs index 427fcd170e..81fec6e363 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs @@ -2,8 +2,10 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using CommunityToolkit.Mvvm.Input; using Microsoft.CmdPal.Core.ViewModels.Models; using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CmdPal.Core.ViewModels; @@ -11,6 +13,13 @@ public partial class DetailsLinkViewModel( IDetailsElement _detailsElement, WeakReference context) : DetailsElementViewModel(_detailsElement, context) { + private static readonly string[] _initProperties = [ + nameof(Text), + nameof(Link), + nameof(IsLink), + nameof(IsText), + nameof(NavigateCommand)]; + private readonly ExtensionObject _dataModel = new(_detailsElement.Data as IDetailsLink); @@ -22,6 +31,8 @@ public partial class DetailsLinkViewModel( public bool IsText => !IsLink; + public RelayCommand? NavigateCommand { get; private set; } + public override void InitializeProperties() { base.InitializeProperties(); @@ -38,9 +49,18 @@ public partial class DetailsLinkViewModel( Text = Link.ToString(); } - UpdateProperty(nameof(Text)); - UpdateProperty(nameof(Link)); - UpdateProperty(nameof(IsLink)); - UpdateProperty(nameof(IsText)); + if (Link is not null) + { + // Custom command to open a link in the default browser or app, + // depending on the link type. + // Binding Link to a Hyperlink(Button).NavigateUri works only for + // certain URI schemes (e.g., http, https) and cannot open file: + // scheme URIs or local files. + NavigateCommand = new RelayCommand( + () => ShellHelpers.OpenInShell(Link.ToString()), + () => Link is not null); + } + + UpdateProperty(_initProperties); } } diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs index 40afae6f9c..41db974f5b 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs @@ -265,6 +265,9 @@ public partial class ShellViewModel : ObservableObject, throw new NotSupportedException(); } + // Clear command bar, ViewModel initialization can already set new commands if it wants to + OnUIThread(() => WeakReferenceMessenger.Default.Send(new(null))); + // Kick off async loading of our ViewModel LoadPageViewModelAsync(pageViewModel, navigationToken) .ContinueWith( @@ -275,9 +278,6 @@ public partial class ShellViewModel : ObservableObject, { newCts.Dispose(); } - - // When we're done loading the page, then update the command bar to match - WeakReferenceMessenger.Default.Send(new(null)); }, navigationToken, TaskContinuationOptions.None, diff --git a/src/modules/cmdpal/CoreCommonProps.props b/src/modules/cmdpal/CoreCommonProps.props index 4c30d3f268..aa091d435e 100644 --- a/src/modules/cmdpal/CoreCommonProps.props +++ b/src/modules/cmdpal/CoreCommonProps.props @@ -1,4 +1,4 @@ - + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs index 9b2234fb16..aea2125573 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs @@ -52,7 +52,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference @@ -15,11 +16,12 @@ + 240 - + + + \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IsEnabledTextBlock.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IsEnabledTextBlock.xaml.cs new file mode 100644 index 0000000000..ffe65bc9f9 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IsEnabledTextBlock.xaml.cs @@ -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.ComponentModel; + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.CmdPal.UI.Controls; + +[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")] +[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")] +public partial class IsEnabledTextBlock : Control +{ + public IsEnabledTextBlock() + { + this.Style = (Style)App.Current.Resources["DefaultIsEnabledTextBlockStyle"]; + } + + protected override void OnApplyTemplate() + { + IsEnabledChanged -= IsEnabledTextBlock_IsEnabledChanged; + SetEnabledState(); + IsEnabledChanged += IsEnabledTextBlock_IsEnabledChanged; + base.OnApplyTemplate(); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", + typeof(string), + typeof(IsEnabledTextBlock), + null); + + [Localizable(true)] + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + private void IsEnabledTextBlock_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + SetEnabledState(); + } + + private void SetEnabledState() + { + VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true); + } +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ContentPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ContentPage.xaml.cs index e9ab57cfa5..c022d82b34 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ContentPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ContentPage.xaml.cs @@ -78,6 +78,12 @@ public sealed partial class ContentPage : Page, WeakReferenceMessenger.Default.Unregister(this); // Clean-up event listeners + if (e.NavigationMode != NavigationMode.New) + { + ViewModel?.SafeCleanup(); + CleanupHelper.Cleanup(this); + } + ViewModel = null; } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs index c3dd7c28bf..788127a5d1 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs @@ -14,7 +14,7 @@ namespace Microsoft.CmdPal.UI.Helpers; /// /// A class that listens for local keyboard events using a Windows hook. /// -internal sealed class LocalKeyboardListener : IDisposable +internal sealed partial class LocalKeyboardListener : IDisposable { /// /// Event that is raised when a key is pressed down. diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/LocalSuppressions.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/LocalSuppressions.cs new file mode 100644 index 0000000000..1b456f66d7 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/LocalSuppressions.cs @@ -0,0 +1,9 @@ +// 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.CodeAnalysis; + +[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.DestroyIconSafeHandle")] +[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.DestroyMenuSafeHandle")] +[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.FreeLibrarySafeHandle")] +[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.UnhookWindowsHookExSafeHandle")] diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj index b41f20a5a1..f577f13898 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj @@ -66,7 +66,9 @@ + + @@ -180,6 +182,18 @@ PreserveNewest + + + + MSBuild:Compile + + + + + + MSBuild:Compile + + MSBuild:Compile diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml index 597072241a..fe1a29dd97 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml @@ -108,6 +108,7 @@ Visibility="{x:Bind IsText, Mode=OneWay}" /> diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml index 5a495a2a21..72b51fd724 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml @@ -33,6 +33,10 @@ x:Key="StringEmptyToBoolConverter" EmptyValue="False" NotEmptyValue="True" /> + @@ -47,22 +51,34 @@ MaxWidth="1000" HorizontalAlignment="Stretch" Spacing="{StaticResource SettingsCardSpacing}"> - - + + + + + + + + + + + + + + - - - - - @@ -84,22 +100,22 @@ - - + - - - - - - + + + + + + + + - @@ -130,11 +146,8 @@ - - - @@ -145,8 +158,13 @@ Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Visibility="{x:Bind ViewModel.HasSettings}" /> - - + - - - diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml index 56ec56fe20..f798134c10 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml @@ -25,7 +25,6 @@ MaxWidth="1000" HorizontalAlignment="Stretch" Spacing="{StaticResource SettingsCardSpacing}"> - @@ -47,9 +46,7 @@ - - diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml index 21cee5407c..121b478189 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml @@ -37,22 +37,20 @@ - - - - + + + + + - - - diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml index e4acb05ae1..7eb6b28f88 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Microsoft.CmdPal.UI.Settings" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:text="using:Windows.UI.Text" xmlns:ui="using:CommunityToolkit.WinUI" xmlns:winuiex="using:WinUIEx" Title="SettingsWindow" @@ -73,14 +74,14 @@ ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}"> - + 28 7,4,8,0 - SemiBold + SemiBold 16 diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw index 76734a5568..ec7ce8b68c 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw @@ -257,8 +257,8 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Disabled Displayed when an extension is disabled - - Enable this extension + + Enable Displayed on a toggle controlling the extension's enabled / disabled state @@ -312,7 +312,13 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Alias - Typing this alias will navigate to this command. Direct aliases navigate as soon as you type the alias. Indirect aliases navigate after typing a trailing space. + A short keyword used to navigate to this command. + + + Alias activation + + + Choose when the alias runs. Direct runs as soon as you type the alias. Indirect runs after a trailing space. Built-in @@ -429,12 +435,15 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Close Close as a verb, as in Close the application - + Direct - + Indirect + + Enter alias + Show status messages @@ -454,7 +463,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Trigger reload of the extension externally with the x-cmdpal://reload command - For Developers + For developers an untitled diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/Settings.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/Settings.xaml index 0413bbe5cb..89c01814eb 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/Settings.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/Settings.xaml @@ -2,7 +2,7 @@ - + 4 diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Themes/Generic.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Themes/Generic.xaml deleted file mode 100644 index 6903d112e3..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Themes/Generic.xaml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs index e8271da371..cbbe365a1b 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs @@ -17,6 +17,7 @@ public class Settings : ISettingsInterface private readonly bool hideKillProcessOnElevatedProcesses; private readonly bool hideExplorerSettingInfo; private readonly bool inMruOrder; + private readonly bool useWindowIcon; public Settings( bool resultsFromVisibleDesktopOnly = false, @@ -27,7 +28,8 @@ public class Settings : ISettingsInterface bool openAfterKillAndClose = false, bool hideKillProcessOnElevatedProcesses = false, bool hideExplorerSettingInfo = true, - bool inMruOrder = true) + bool inMruOrder = true, + bool useWindowIcon = true) { this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly; this.subtitleShowPid = subtitleShowPid; @@ -38,6 +40,7 @@ public class Settings : ISettingsInterface this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses; this.hideExplorerSettingInfo = hideExplorerSettingInfo; this.inMruOrder = inMruOrder; + this.useWindowIcon = useWindowIcon; } public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly; @@ -57,4 +60,6 @@ public class Settings : ISettingsInterface public bool HideExplorerSettingInfo => hideExplorerSettingInfo; public bool InMruOrder => inMruOrder; + + public bool UseWindowIcon => useWindowIcon; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/LocalSuppressions.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/LocalSuppressions.cs new file mode 100644 index 0000000000..87a0cf5673 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/LocalSuppressions.cs @@ -0,0 +1,6 @@ +// 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.CodeAnalysis; + +[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.SysFreeStringSafeHandle")] diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/IClipboardMetadataProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/IClipboardMetadataProvider.cs new file mode 100644 index 0000000000..9b73ade32b --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/IClipboardMetadataProvider.cs @@ -0,0 +1,35 @@ +// 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 Microsoft.CmdPal.Ext.ClipboardHistory.Models; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +/// +/// Abstraction for providers that can extract metadata and offer actions for a clipboard context. +/// +internal interface IClipboardMetadataProvider +{ + /// + /// Gets the section title to show in the UI for this provider's metadata. + /// + string SectionTitle { get; } + + /// + /// Returns true if this provider can produce metadata for the given item. + /// + bool CanHandle(ClipboardItem item); + + /// + /// Returns metadata elements for the UI. Caller decides section grouping. + /// + IEnumerable GetDetails(ClipboardItem item); + + /// + /// Returns context actions to be appended to MoreCommands. Use unique IDs for de-duplication. + /// + IEnumerable GetActions(ClipboardItem item); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadata.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadata.cs new file mode 100644 index 0000000000..429f6341f3 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadata.cs @@ -0,0 +1,12 @@ +// 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 Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal sealed record ImageMetadata( + uint Width, + uint Height, + double DpiX, + double DpiY, + ulong? StorageSize); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadataAnalyzer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadataAnalyzer.cs new file mode 100644 index 0000000000..e69a7d3d9c --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadataAnalyzer.cs @@ -0,0 +1,55 @@ +// 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.Threading.Tasks; +using Windows.Graphics.Imaging; +using Windows.Storage.Streams; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal static class ImageMetadataAnalyzer +{ + /// + /// Reads image metadata from a RandomAccessStreamReference without decoding pixels. + /// Returns oriented dimensions (EXIF rotation applied). + /// + public static async Task GetAsync(RandomAccessStreamReference reference) + { + ArgumentNullException.ThrowIfNull(reference); + + using IRandomAccessStream ras = await reference.OpenReadAsync().AsTask().ConfigureAwait(false); + var sizeBytes = TryGetSize(ras); + + // BitmapDecoder does not decode pixel data unless you ask it to, + // so this is fast and memory-friendly. + var decoder = await BitmapDecoder.CreateAsync(ras).AsTask().ConfigureAwait(false); + + // OrientedPixelWidth/Height account for EXIF orientation + var width = decoder.OrientedPixelWidth; + var height = decoder.OrientedPixelHeight; + + return new ImageMetadata( + Width: width, + Height: height, + DpiX: decoder.DpiX, + DpiY: decoder.DpiY, + StorageSize: sizeBytes); + } + + private static ulong? TryGetSize(IRandomAccessStream s) + { + try + { + // On file-backed streams this is accurate. + // On some URI/virtual streams this may be unsupported or 0. + var size = s.Size; + return size == 0 ? (ulong?)0 : size; + } + catch + { + return null; + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadataProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadataProvider.cs new file mode 100644 index 0000000000..09a3f33f2e --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ImageMetadataProvider.cs @@ -0,0 +1,60 @@ +// 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.CmdPal.Ext.ClipboardHistory.Models; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal sealed class ImageMetadataProvider : IClipboardMetadataProvider +{ + public string SectionTitle => "Image metadata"; + + public bool CanHandle(ClipboardItem item) => item.IsImage; + + public IEnumerable GetDetails(ClipboardItem item) + { + var result = new List(); + if (!CanHandle(item) || item.ImageData is null) + { + return result; + } + + try + { + var metadata = ImageMetadataAnalyzer.GetAsync(item.ImageData).GetAwaiter().GetResult(); + + result.Add(new DetailsElement + { + Key = "Dimensions", + Data = new DetailsLink($"{metadata.Width} x {metadata.Height}"), + }); + result.Add(new DetailsElement + { + Key = "DPI", + Data = new DetailsLink($"{metadata.DpiX:0.###} x {metadata.DpiY:0.###}"), + }); + + if (metadata.StorageSize != null) + { + result.Add(new DetailsElement + { + Key = "Storage size", + Data = new DetailsLink(SizeFormatter.FormatSize(metadata.StorageSize.Value)), + }); + } + } + catch (Exception ex) + { + Logger.LogDebug("Failed to retrieve image metadata:" + ex); + } + + return result; + } + + public IEnumerable GetActions(ClipboardItem item) => []; +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/LineEndingType.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/LineEndingType.cs new file mode 100644 index 0000000000..1274d1ace9 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/LineEndingType.cs @@ -0,0 +1,14 @@ +// 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 Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal enum LineEndingType +{ + None, + Windows, // \r\n (CRLF) + Unix, // \n (LF) + Mac, // \r (CR) + Mixed, +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ProviderAction.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ProviderAction.cs new file mode 100644 index 0000000000..1827fa8744 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/ProviderAction.cs @@ -0,0 +1,14 @@ +// 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.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +/// +/// Represents an action exposed by a metadata provider. +/// +/// Unique identifier for de-duplication (case-insensitive). +/// The actual context menu item to be shown. +internal readonly record struct ProviderAction(string Id, CommandContextItem Action); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/SizeFormatter.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/SizeFormatter.cs new file mode 100644 index 0000000000..a08ab32bc2 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/SizeFormatter.cs @@ -0,0 +1,49 @@ +// 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.Globalization; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +/// +/// Utility for formatting byte sizes to a human-readable string. +/// +internal static class SizeFormatter +{ + private const long KB = 1024; + private const long MB = 1024 * KB; + private const long GB = 1024 * MB; + + public static string FormatSize(long bytes) + { + return bytes switch + { + >= GB => string.Format(CultureInfo.CurrentCulture, "{0:F2} GB", (double)bytes / GB), + >= MB => string.Format(CultureInfo.CurrentCulture, "{0:F2} MB", (double)bytes / MB), + >= KB => string.Format(CultureInfo.CurrentCulture, "{0:F2} KB", (double)bytes / KB), + _ => string.Format(CultureInfo.CurrentCulture, "{0} B", bytes), + }; + } + + public static string FormatSize(ulong bytes) + { + // Use double for division to avoid overflow; thresholds mirror long version + if (bytes >= (ulong)GB) + { + return string.Format(CultureInfo.CurrentCulture, "{0:F2} GB", bytes / (double)GB); + } + + if (bytes >= (ulong)MB) + { + return string.Format(CultureInfo.CurrentCulture, "{0:F2} MB", bytes / (double)MB); + } + + if (bytes >= (ulong)KB) + { + return string.Format(CultureInfo.CurrentCulture, "{0:F2} KB", bytes / (double)KB); + } + + return string.Format(CultureInfo.CurrentCulture, "{0} B", bytes); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextFileSystemMetadataProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextFileSystemMetadataProvider.cs new file mode 100644 index 0000000000..a51444a3af --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextFileSystemMetadataProvider.cs @@ -0,0 +1,138 @@ +// 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.Globalization; +using System.IO; +using ManagedCommon; +using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CmdPal.Ext.ClipboardHistory.Models; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +/// +/// Detects when text content is a valid existing file or directory path and exposes basic metadata. +/// +internal sealed class TextFileSystemMetadataProvider : IClipboardMetadataProvider +{ + public string SectionTitle => "File"; + + public bool CanHandle(ClipboardItem item) + { + ArgumentNullException.ThrowIfNull(item); + + if (!item.IsText || string.IsNullOrWhiteSpace(item.Content)) + { + return false; + } + + var text = PathHelper.Unquote(item.Content); + return PathHelper.IsValidFilePath(text); + } + + public IEnumerable GetDetails(ClipboardItem item) + { + ArgumentNullException.ThrowIfNull(item); + + var result = new List(); + if (!item.IsText || string.IsNullOrWhiteSpace(item.Content)) + { + return result; + } + + var path = PathHelper.Unquote(item.Content); + + if (PathHelper.IsSlow(path) || !PathHelper.Exists(path, out var isDirectory)) + { + result.Add(new DetailsElement { Key = "Name", Data = new DetailsLink(Path.GetFileName(path)) }); + result.Add(new DetailsElement { Key = "Location", Data = new DetailsLink(UrlHelper.NormalizeUrl(path), path) }); + return result; + } + + try + { + if (!isDirectory) + { + var fi = new FileInfo(path); + result.Add(new DetailsElement { Key = "Name", Data = new DetailsLink(fi.Name) }); + result.Add(new DetailsElement { Key = "Location", Data = new DetailsLink(UrlHelper.NormalizeUrl(fi.FullName), fi.FullName) }); + result.Add(new DetailsElement { Key = "Type", Data = new DetailsLink(fi.Extension) }); + result.Add(new DetailsElement { Key = "Size", Data = new DetailsLink(SizeFormatter.FormatSize(fi.Length)) }); + result.Add(new DetailsElement { Key = "Modified", Data = new DetailsLink(fi.LastWriteTime.ToString(CultureInfo.CurrentCulture)) }); + result.Add(new DetailsElement { Key = "Created", Data = new DetailsLink(fi.CreationTime.ToString(CultureInfo.CurrentCulture)) }); + } + else + { + var di = new DirectoryInfo(path); + result.Add(new DetailsElement { Key = "Name", Data = new DetailsLink(di.Name) }); + result.Add(new DetailsElement { Key = "Location", Data = new DetailsLink(UrlHelper.NormalizeUrl(di.FullName), di.FullName) }); + result.Add(new DetailsElement { Key = "Type", Data = new DetailsLink("Folder") }); + result.Add(new DetailsElement { Key = "Modified", Data = new DetailsLink(di.LastWriteTime.ToString(CultureInfo.CurrentCulture)) }); + result.Add(new DetailsElement { Key = "Created", Data = new DetailsLink(di.CreationTime.ToString(CultureInfo.CurrentCulture)) }); + } + } + catch (Exception ex) + { + Logger.LogError("Failed to retrieve file system metadata.", ex); + } + + return result; + } + + public IEnumerable GetActions(ClipboardItem item) + { + ArgumentNullException.ThrowIfNull(item); + + if (!item.IsText || string.IsNullOrWhiteSpace(item.Content)) + { + yield break; + } + + var path = PathHelper.Unquote(item.Content); + + if (PathHelper.IsSlow(path) || !PathHelper.Exists(path, out var isDirectory)) + { + // One anything + var open = new CommandContextItem(new OpenFileCommand(path)) { RequestedShortcut = KeyChords.OpenUrl }; + yield return new ProviderAction(WellKnownActionIds.Open, open); + + yield break; + } + + if (!isDirectory) + { + // Open file + var open = new CommandContextItem(new OpenFileCommand(path)) { RequestedShortcut = KeyChords.OpenUrl }; + yield return new ProviderAction(WellKnownActionIds.Open, open); + + // Show in folder (select) + var show = new CommandContextItem(new ShowFileInFolderCommand(path)) { RequestedShortcut = WellKnownKeyChords.OpenFileLocation }; + yield return new ProviderAction(WellKnownActionIds.OpenLocation, show); + + // Copy path + var copy = new CommandContextItem(new CopyPathCommand(path)) { RequestedShortcut = WellKnownKeyChords.CopyFilePath }; + yield return new ProviderAction(WellKnownActionIds.CopyPath, copy); + + // Open in console at file location + var openConsole = new CommandContextItem(OpenInConsoleCommand.FromFile(path)) { RequestedShortcut = WellKnownKeyChords.OpenInConsole }; + yield return new ProviderAction(WellKnownActionIds.OpenConsole, openConsole); + } + else + { + // Open folder + var openFolder = new CommandContextItem(new OpenFileCommand(path)) { RequestedShortcut = KeyChords.OpenUrl }; + yield return new ProviderAction(WellKnownActionIds.Open, openFolder); + + // Open in console + var openConsole = new CommandContextItem(OpenInConsoleCommand.FromDirectory(path)) { RequestedShortcut = WellKnownKeyChords.OpenInConsole }; + yield return new ProviderAction(WellKnownActionIds.OpenConsole, openConsole); + + // Copy path + var copy = new CommandContextItem(new CopyPathCommand(path)) { RequestedShortcut = WellKnownKeyChords.CopyFilePath }; + yield return new ProviderAction(WellKnownActionIds.CopyPath, copy); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadata.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadata.cs new file mode 100644 index 0000000000..726a15c37e --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadata.cs @@ -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. + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal sealed record TextMetadata +{ + public int CharacterCount { get; init; } + + public int WordCount { get; init; } + + public int SentenceCount { get; init; } + + public int LineCount { get; init; } + + public int ParagraphCount { get; init; } + + public LineEndingType LineEnding { get; init; } + + public override string ToString() + { + return $"Characters: {CharacterCount}, Words: {WordCount}, Sentences: {SentenceCount}, Lines: {LineCount}, Paragraphs: {ParagraphCount}, Line Ending: {LineEnding}"; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs new file mode 100644 index 0000000000..83992f6428 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs @@ -0,0 +1,109 @@ +// 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.Linq; +using System.Text.RegularExpressions; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal partial class TextMetadataAnalyzer +{ + public TextMetadata Analyze(string input) + { + ArgumentNullException.ThrowIfNull(input); + + return new TextMetadata + { + CharacterCount = input.Length, + WordCount = CountWords(input), + SentenceCount = CountSentences(input), + LineCount = CountLines(input), + ParagraphCount = CountParagraphs(input), + LineEnding = DetectLineEnding(input), + }; + } + + private LineEndingType DetectLineEnding(string text) + { + var crlfCount = Regex.Matches(text, "\r\n").Count; + var lfCount = Regex.Matches(text, "(? 0 ? 1 : 0) + (lfCount > 0 ? 1 : 0) + (crCount > 0 ? 1 : 0); + + if (endingTypes > 1) + { + return LineEndingType.Mixed; + } + + if (crlfCount > 0) + { + return LineEndingType.Windows; + } + + if (lfCount > 0) + { + return LineEndingType.Unix; + } + + if (crCount > 0) + { + return LineEndingType.Mac; + } + + return LineEndingType.None; + } + + private int CountLines(string text) + { + if (string.IsNullOrEmpty(text)) + { + return 0; + } + + return text.Count(c => c == '\n') + 1; + } + + private int CountParagraphs(string text) + { + if (string.IsNullOrEmpty(text)) + { + return 0; + } + + var paragraphs = ParagraphsRegex() + .Split(text) + .Count(static p => !string.IsNullOrWhiteSpace(p)); + + return paragraphs > 0 ? paragraphs : 1; + } + + private int CountWords(string text) + { + if (string.IsNullOrEmpty(text)) + { + return 0; + } + + return Regex.Matches(text, @"\b\w+\b").Count; + } + + private int CountSentences(string text) + { + if (string.IsNullOrEmpty(text)) + { + return 0; + } + + var matches = SentencesRegex().Matches(text); + return matches.Count > 0 ? matches.Count : (text.Trim().Length > 0 ? 1 : 0); + } + + [GeneratedRegex(@"(\r?\n){2,}")] + private static partial Regex ParagraphsRegex(); + + [GeneratedRegex(@"[.!?]+(?=\s|$)")] + private static partial Regex SentencesRegex(); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataProvider.cs new file mode 100644 index 0000000000..86e2a32270 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataProvider.cs @@ -0,0 +1,63 @@ +// 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.Globalization; +using Microsoft.CmdPal.Ext.ClipboardHistory.Models; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +internal sealed class TextMetadataProvider : IClipboardMetadataProvider +{ + public string SectionTitle => "Text statistics"; + + public bool CanHandle(ClipboardItem item) => item.IsText; + + public IEnumerable GetDetails(ClipboardItem item) + { + var result = new List(); + if (!CanHandle(item) || string.IsNullOrEmpty(item.Content)) + { + return result; + } + + var r = new TextMetadataAnalyzer().Analyze(item.Content); + + result.Add(new DetailsElement + { + Key = "Characters", + Data = new DetailsLink(r.CharacterCount.ToString(CultureInfo.CurrentCulture)), + }); + result.Add(new DetailsElement + { + Key = "Words", + Data = new DetailsLink(r.WordCount.ToString(CultureInfo.CurrentCulture)), + }); + result.Add(new DetailsElement + { + Key = "Sentences", + Data = new DetailsLink(r.SentenceCount.ToString(CultureInfo.CurrentCulture)), + }); + result.Add(new DetailsElement + { + Key = "Lines", + Data = new DetailsLink(r.LineCount.ToString(CultureInfo.CurrentCulture)), + }); + result.Add(new DetailsElement + { + Key = "Paragraphs", + Data = new DetailsLink(r.ParagraphCount.ToString(CultureInfo.CurrentCulture)), + }); + result.Add(new DetailsElement + { + Key = "Line Ending", + Data = new DetailsLink(r.LineEnding.ToString()), + }); + + return result; + } + + public IEnumerable GetActions(ClipboardItem item) => []; +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/WebLinkMetadataProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/WebLinkMetadataProvider.cs new file mode 100644 index 0000000000..0a2afc3e01 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/WebLinkMetadataProvider.cs @@ -0,0 +1,113 @@ +// 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.Globalization; +using System.Linq; +using Microsoft.CmdPal.Ext.ClipboardHistory.Models; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +/// +/// Detects web links in text and shows normalized URL and key parts. +/// +internal sealed class WebLinkMetadataProvider : IClipboardMetadataProvider +{ + public string SectionTitle => "Link"; + + public bool CanHandle(ClipboardItem item) + { + if (!item.IsText || string.IsNullOrWhiteSpace(item.Content)) + { + return false; + } + + if (!UrlHelper.IsValidUrl(item.Content)) + { + return false; + } + + var normalized = UrlHelper.NormalizeUrl(item.Content); + if (!Uri.TryCreate(normalized, UriKind.Absolute, out var uri)) + { + return false; + } + + // Exclude file: scheme; it's handled by TextFileSystemMetadataProvider + return !uri.Scheme.Equals(Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase); + } + + public IEnumerable GetDetails(ClipboardItem item) + { + var result = new List(); + if (!item.IsText || string.IsNullOrWhiteSpace(item.Content)) + { + return result; + } + + try + { + var normalized = UrlHelper.NormalizeUrl(item.Content); + if (!Uri.TryCreate(normalized, UriKind.Absolute, out var uri)) + { + return result; + } + + // Skip file: at runtime as well (defensive) + if (uri.Scheme.Equals(Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)) + { + return result; + } + + result.Add(new DetailsElement { Key = "URL", Data = new DetailsLink(normalized) }); + result.Add(new DetailsElement { Key = "Host", Data = new DetailsLink(uri.Host) }); + + if (!uri.IsDefaultPort) + { + result.Add(new DetailsElement { Key = "Port", Data = new DetailsLink(uri.Port.ToString(CultureInfo.CurrentCulture)) }); + } + + if (!string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/") + { + result.Add(new DetailsElement { Key = "Path", Data = new DetailsLink(uri.AbsolutePath) }); + } + + if (!string.IsNullOrEmpty(uri.Query)) + { + var q = uri.Query; + var count = q.Count(static c => c == '&') + (q.Length > 1 ? 1 : 0); + result.Add(new DetailsElement { Key = "Query params", Data = new DetailsLink(count.ToString(CultureInfo.CurrentCulture)) }); + } + + if (!string.IsNullOrEmpty(uri.Fragment)) + { + result.Add(new DetailsElement { Key = "Fragment", Data = new DetailsLink(uri.Fragment) }); + } + } + catch + { + // ignore malformed inputs + } + + return result; + } + + public IEnumerable GetActions(ClipboardItem item) + { + if (!CanHandle(item)) + { + yield break; + } + + var normalized = UrlHelper.NormalizeUrl(item.Content!); + + var open = new CommandContextItem(new OpenUrlCommand(normalized)) + { + RequestedShortcut = KeyChords.OpenUrl, + }; + yield return new ProviderAction(WellKnownActionIds.Open, open); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/WellKnownActionIds.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/WellKnownActionIds.cs new file mode 100644 index 0000000000..7fa2a74aea --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/WellKnownActionIds.cs @@ -0,0 +1,16 @@ +// 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 Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; + +/// +/// Well-known action id constants used to de-duplicate provider actions. +/// +internal static class WellKnownActionIds +{ + public const string Open = "open"; + public const string OpenLocation = "openLocation"; + public const string CopyPath = "copyPath"; + public const string OpenConsole = "openConsole"; +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/UrlHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/UrlHelper.cs index 60e7851761..fe160e4c1b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/UrlHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/UrlHelper.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.IO; +using Microsoft.CmdPal.Core.Common.Helpers; namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers; @@ -31,7 +31,7 @@ internal static class UrlHelper } // Check if it's a valid file path (local or network) - if (IsValidFilePath(url)) + if (PathHelper.IsValidFilePath(url)) { return true; } @@ -78,7 +78,7 @@ internal static class UrlHelper url = url.Trim(); // If it's a valid file path, convert to file:// URI - if (IsValidFilePath(url) && !url.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) + if (!url.StartsWith("file://", StringComparison.OrdinalIgnoreCase) && PathHelper.IsValidFilePath(url)) { try { @@ -105,40 +105,4 @@ internal static class UrlHelper return url; } - - /// - /// Checks if a string represents a valid file path (local or network) - /// - /// The string to check - /// True if the string is a valid file path, false otherwise - private static bool IsValidFilePath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - return false; - } - - try - { - // Check for UNC paths (network paths starting with \\) - if (path.StartsWith(@"\\", StringComparison.Ordinal)) - { - // Basic UNC path validation: \\server\share or \\server\share\path - var parts = path.Substring(2).Split('\\', StringSplitOptions.RemoveEmptyEntries); - return parts.Length >= 2; // At minimum: server and share - } - - // Check for drive letters (C:\ or C:) - if (path.Length >= 2 && char.IsLetter(path[0]) && path[1] == ':') - { - return true; - } - - return false; - } - catch - { - return false; - } - } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj index e3d17fb500..b0c0617c34 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj @@ -10,6 +10,7 @@ enable + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs index 9b5aae6f7d..865d8f6b91 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs @@ -9,6 +9,7 @@ using System.Linq; using Microsoft.CmdPal.Common.Commands; using Microsoft.CmdPal.Ext.ClipboardHistory.Commands; using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers; +using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; @@ -16,13 +17,20 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models; internal sealed partial class ClipboardListItem : ListItem { + private static readonly IClipboardMetadataProvider[] MetadataProviders = + [ + new ImageMetadataProvider(), + new TextFileSystemMetadataProvider(), + new WebLinkMetadataProvider(), + new TextMetadataProvider(), + ]; + private readonly SettingsManager _settingsManager; private readonly ClipboardItem _item; private readonly CommandContextItem _deleteContextMenuItem; private readonly CommandContextItem? _pasteCommand; private readonly CommandContextItem? _copyCommand; - private readonly CommandContextItem? _openUrlCommand; private readonly Lazy
_lazyDetails; public override IDetails? Details @@ -73,26 +81,11 @@ internal sealed partial class ClipboardListItem : ListItem _pasteCommand = new CommandContextItem(new PasteCommand(_item, ClipboardFormat.Text, _settingsManager)); _copyCommand = new CommandContextItem(new CopyCommand(_item, ClipboardFormat.Text)); - - // Check if the text content is a valid URL and add OpenUrl command - if (UrlHelper.IsValidUrl(_item.Content ?? string.Empty)) - { - var normalizedUrl = UrlHelper.NormalizeUrl(_item.Content ?? string.Empty); - _openUrlCommand = new CommandContextItem(new OpenUrlCommand(normalizedUrl)) - { - RequestedShortcut = KeyChords.OpenUrl, - }; - } - else - { - _openUrlCommand = null; - } } else { _pasteCommand = null; _copyCommand = null; - _openUrlCommand = null; } RefreshCommands(); @@ -163,27 +156,74 @@ internal sealed partial class ClipboardListItem : ListItem commands.Add(firstCommand); } - if (_openUrlCommand != null) + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + var temp = new List(); + foreach (var provider in MetadataProviders) { - commands.Add(_openUrlCommand); + if (!provider.CanHandle(_item)) + { + continue; + } + + foreach (var action in provider.GetActions(_item)) + { + if (string.IsNullOrEmpty(action.Id) || !seen.Add(action.Id)) + { + continue; + } + + temp.Add(action.Action); + } + } + + if (temp.Count > 0) + { + if (commands.Count > 0) + { + commands.Add(new Separator()); + } + + commands.AddRange(temp); } commands.Add(new Separator()); commands.Add(_deleteContextMenuItem); - return commands.ToArray(); + return [.. commands]; } private Details CreateDetails() { - IDetailsElement[] metadata = - [ - new DetailsElement + List metadata = []; + + foreach (var provider in MetadataProviders) + { + if (provider.CanHandle(_item)) { - Key = "Copied on", - Data = new DetailsLink(_item.Timestamp.DateTime.ToString(DateTimeFormatInfo.CurrentInfo)), + var details = provider.GetDetails(_item); + if (details.Any()) + { + metadata.Add(new DetailsElement + { + Key = provider.SectionTitle, + Data = new DetailsSeparator(), + }); + + metadata.AddRange(details); + } } - ]; + } + + metadata.Add(new DetailsElement + { + Key = "General", + Data = new DetailsSeparator(), + }); + metadata.Add(new DetailsElement + { + Key = "Copied", + Data = new DetailsLink(_item.Timestamp.DateTime.ToString(DateTimeFormatInfo.CurrentInfo)), + }); if (_item.IsImage) { @@ -193,7 +233,7 @@ internal sealed partial class ClipboardListItem : ListItem { Title = _item.GetDataType(), HeroImage = heroImage, - Metadata = metadata, + Metadata = [.. metadata], }; } @@ -203,7 +243,7 @@ internal sealed partial class ClipboardListItem : ListItem { Title = _item.GetDataType(), Body = $"```text\n{_item.Content}\n```", - Metadata = metadata, + Metadata = [.. metadata], }; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs index cda1f6ccfc..f8695cae1b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties { // 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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -187,7 +187,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item. /// public static string settings_confirm_delete_description { get { @@ -196,7 +196,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties { } /// - /// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item. + /// Looks up a localized string similar to Ask for confirmation before deleting items. /// public static string settings_confirm_delete_title { get { @@ -205,7 +205,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Keep items in clipboard history after pasting. /// public static string settings_keep_after_paste_description { get { @@ -214,7 +214,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties { } /// - /// Looks up a localized string similar to Keep items in clipboard history after pasting. + /// Looks up a localized string similar to Keep items after pasting. /// public static string settings_keep_after_paste_title { get { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx index 0af6ee4cfc..56d0805871 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx @@ -151,16 +151,16 @@ Deleted from clipboard history - - - Keep items in clipboard history after pasting + + Keep items after pasting + - Show a confirmation dialog when manually deleting an item + Ask for confirmation before deleting items - + Show a confirmation dialog when manually deleting an item Delete item? diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/LocalSuppressions.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/LocalSuppressions.cs new file mode 100644 index 0000000000..dc699880c9 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/LocalSuppressions.cs @@ -0,0 +1,6 @@ +// 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.CodeAnalysis; + +[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.LocalFreeSafeHandle")] diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/NetworkConnectionProperties.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/NetworkConnectionProperties.cs index 69e5c2ac78..f14011440d 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/NetworkConnectionProperties.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/NetworkConnectionProperties.cs @@ -27,6 +27,14 @@ internal sealed class NetworkConnectionProperties /// private const int GreenCircleCharacter = 128994; + /// + /// Decimal unicode value for green circle emoji. + /// We need to generate it in the code because it does not render using Markdown emoji syntax or Unicode character syntax. + /// + /// + /// + private const int RedCircleCharacter = 128308; + /// /// Gets the name of the adapter /// @@ -170,7 +178,7 @@ internal sealed class NetworkConnectionProperties internal string GetAdapterDetails() { return $"**{Resources.Microsoft_plugin_sys_AdapterName}:** {Adapter}" + - $"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : ":red_circle: " + Resources.Microsoft_plugin_sys_Disconnected) + + $"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : char.ConvertFromUtf32(RedCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Disconnected) + $"\n\n**{Resources.Microsoft_plugin_sys_PhysicalAddress}:** {PhysicalAddress}" + $"\n\n**{Resources.Microsoft_plugin_sys_Speed}:** {GetFormattedSpeedValue(Speed)}" + $"\n\n**{Resources.Microsoft_plugin_sys_Type}:** {GetAdapterTypeAsString(Type)}" + @@ -184,7 +192,7 @@ internal sealed class NetworkConnectionProperties internal string GetConnectionDetails() { return $"**{Resources.Microsoft_plugin_sys_ConnectionName}:** {ConnectionName}" + - $"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : ":red_circle: " + Resources.Microsoft_plugin_sys_Disconnected) + + $"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : char.ConvertFromUtf32(RedCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Disconnected) + $"\n\n**{Resources.Microsoft_plugin_sys_Type}:** {GetAdapterTypeAsString(Type)}" + $"\n\n**{Resources.Microsoft_plugin_sys_Suffix}:** {Suffix}" + CreateIpInfoForDetailsText($"**{Resources.Microsoft_plugin_sys_Ip4Address}:** ", IPv4) + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs index 695eaa2c83..c215d0f300 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs @@ -2,11 +2,16 @@ // 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.Diagnostics; +using System.Drawing; +using System.IO; using Microsoft.CmdPal.Ext.WindowWalker.Components; +using Microsoft.CmdPal.Ext.WindowWalker.Helpers; using Microsoft.CmdPal.Ext.WindowWalker.Properties; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.Storage.Streams; namespace Microsoft.CmdPal.Ext.WindowWalker.Commands; @@ -16,20 +21,53 @@ internal sealed partial class SwitchToWindowCommand : InvokableCommand public SwitchToWindowCommand(Window? window) { + Icon = Icons.GenericAppIcon; // Fallback to default icon Name = Resources.switch_to_command_title; _window = window; if (_window is not null) { - var p = Process.GetProcessById((int)_window.Process.ProcessID); - if (p is not null) + // Use window icon + if (SettingsManager.Instance.UseWindowIcon) { - try + if (_window.TryGetWindowIcon(out var icon) && icon is not null) { - var processFileName = p.MainModule?.FileName; - Icon = new IconInfo(processFileName); + try + { + using var bitmap = icon.ToBitmap(); + using var memoryStream = new MemoryStream(); + bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png); + var raStream = new InMemoryRandomAccessStream(); + using var outputStream = raStream.GetOutputStreamAt(0); + using var dataWriter = new DataWriter(outputStream); + dataWriter.WriteBytes(memoryStream.ToArray()); + dataWriter.StoreAsync().AsTask().Wait(); + dataWriter.FlushAsync().AsTask().Wait(); + Icon = IconInfo.FromStream(raStream); + } + catch + { + } + finally + { + icon.Dispose(); + } } - catch + } + + // Use process icon + else + { + var p = Process.GetProcessById((int)_window.Process.ProcessID); + if (p is not null) { + try + { + var processFileName = p.MainModule?.FileName; + Icon = new IconInfo(processFileName); + } + catch + { + } } } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs index aebf9a77fd..c071d55e80 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs @@ -188,6 +188,62 @@ internal sealed class Window thread.Start(); } + /// + /// Tries to get the window icon. + /// + /// The window icon if found; otherwise, null. + /// True if an icon was found; otherwise, false. + internal bool TryGetWindowIcon(out System.Drawing.Icon? icon) + { + icon = null; + + if (hwnd == IntPtr.Zero) + { + return false; + } + + // Try WM_GETICON with SendMessageTimeout + if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_BIG, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out var result) != 0 && result != 0) + { + icon = System.Drawing.Icon.FromHandle((IntPtr)result); + NativeMethods.DestroyIcon((IntPtr)result); + return true; + } + + if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_SMALL, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out result) != 0 && result != 0) + { + icon = System.Drawing.Icon.FromHandle((IntPtr)result); + NativeMethods.DestroyIcon((IntPtr)result); + return true; + } + + if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_SMALL2, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out result) != 0 && result != 0) + { + icon = System.Drawing.Icon.FromHandle((IntPtr)result); + NativeMethods.DestroyIcon((IntPtr)result); + return true; + } + + // Fallback to GetClassLongPtr + var iconHandle = NativeMethods.GetClassLongPtr(hwnd, Win32Constants.GCLP_HICON); + if (iconHandle != IntPtr.Zero) + { + icon = System.Drawing.Icon.FromHandle(iconHandle); + NativeMethods.DestroyIcon((IntPtr)iconHandle); + return true; + } + + iconHandle = NativeMethods.GetClassLongPtr(hwnd, Win32Constants.GCLP_HICONSM); + if (iconHandle != IntPtr.Zero) + { + icon = System.Drawing.Icon.FromHandle(iconHandle); + NativeMethods.DestroyIcon((IntPtr)iconHandle); + return true; + } + + return false; + } + /// /// Converts the window name to string along with the process name /// @@ -324,7 +380,7 @@ internal sealed class Window // Correct the process data if the window belongs to a uwp app hosted by 'ApplicationFrameHost.exe' // (This only works if the window isn't minimized. For minimized windows the required child window isn't assigned.) - if (string.Equals(_handlesToProcessCache[hWindow].Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase)) + if (_handlesToProcessCache[hWindow].IsUwpAppFrameHost) { new Task(() => { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/WindowProcess.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/WindowProcess.cs index af3730cada..2dfbbcf429 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/WindowProcess.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/WindowProcess.cs @@ -23,7 +23,7 @@ internal sealed class WindowProcess /// /// An indicator if the window belongs to an 'Universal Windows Platform (UWP)' process /// - private readonly bool _isUwpAppFrameHost; + private bool _isUwpAppFrameHost; /// /// Gets the id of the process @@ -126,6 +126,14 @@ internal sealed class WindowProcess get; private set; } + /// + /// Gets the type of the process (UWP app, packaged Win32 app, unpackaged Win32 app, ...). + /// + internal ProcessPackagingInfo ProcessType + { + get; private set; + } + /// /// Initializes a new instance of the class. /// @@ -134,13 +142,10 @@ internal sealed class WindowProcess /// New process name. internal WindowProcess(uint pid, uint tid, string name) { + ProcessType = ProcessPackagingInfo.Empty; UpdateProcessInfo(pid, tid, name); - ProcessType = ProcessPackagingInspector.Inspect((int)pid); - _isUwpAppFrameHost = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase); } - public ProcessPackagingInfo ProcessType { get; private set; } - /// /// Updates the process information of the instance. /// @@ -156,6 +161,10 @@ internal sealed class WindowProcess // Process can be elevated only if process id is not 0 (Dummy value on error) IsFullAccessDenied = (pid != 0) ? TestProcessAccessUsingAllAccessFlag(pid) : false; + + // Update process type + ProcessType = ProcessPackagingInspector.Inspect((int)pid); + _isUwpAppFrameHost = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase); } /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs index e77acb56cf..de3827c76a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs @@ -23,4 +23,6 @@ public interface ISettingsInterface public bool HideExplorerSettingInfo { get; } public bool InMruOrder { get; } + + public bool UseWindowIcon { get; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs index 382d1a56d1..57d65a305e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs @@ -84,6 +84,12 @@ public static partial class NativeMethods [DllImport("user32.dll")] public static extern int SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam, int fuFlags, int uTimeout, out int lpdwResult); + [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] + public static extern IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + internal static extern bool DestroyIcon(IntPtr hIcon); + [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); @@ -143,6 +149,41 @@ public static class Win32Constants /// public const int SC_CLOSE = 0xF060; + /// + /// Sent to a window to retrieve a handle to the large or small icon associated with a window. + /// + public const uint WM_GETICON = 0x007F; + + /// + /// Retrieve the large icon for the window. + /// + public const int ICON_BIG = 1; + + /// + /// Retrieve the small icon for the window. + /// + public const int ICON_SMALL = 0; + + /// + /// Retrieve the small icon provided by the application. + /// + public const int ICON_SMALL2 = 2; + + /// + /// The function returns if the receiving thread does not respond within the timeout period. + /// + public const int SMTO_ABORTIFHUNG = 0x0002; + + /// + /// Retrieves a handle to the icon associated with the class. + /// + public const int GCLP_HICON = -14; + + /// + /// Retrieves a handle to the small icon associated with the class. + /// + public const int GCLP_HICONSM = -34; + /// /// RPC call succeeded /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ProcessPackagingInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ProcessPackagingInfo.cs index f1d3c5e09d..1a1321a9d6 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ProcessPackagingInfo.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ProcessPackagingInfo.cs @@ -11,4 +11,13 @@ internal sealed record ProcessPackagingInfo( bool IsAppContainer, string? PackageFullName, int? LastError -); +) +{ + public static ProcessPackagingInfo Empty { get; } = new( + Pid: 0, + Kind: ProcessPackagingKind.Unknown, + HasPackageIdentity: false, + IsAppContainer: false, + PackageFullName: null, + LastError: null); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs index b2a248beca..1b223fea9b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs @@ -70,6 +70,12 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface Resources.windowwalker_SettingInMruOrder_Description, true); + private readonly ToggleSetting _useWindowIcon = new( + Namespaced(nameof(UseWindowIcon)), + Resources.windowwalker_SettingUseWindowIcon, + Resources.windowwalker_SettingUseWindowIcon_Description, + true); + public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value; public bool SubtitleShowPid => _subtitleShowPid.Value; @@ -88,6 +94,8 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface public bool InMruOrder => _inMruOrder.Value; + public bool UseWindowIcon => _useWindowIcon.Value; + internal static string SettingsJsonPath() { var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); @@ -110,6 +118,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface Settings.Add(_hideKillProcessOnElevatedProcesses); Settings.Add(_hideExplorerSettingInfo); Settings.Add(_inMruOrder); + Settings.Add(_useWindowIcon); // Load settings from file upon initialization LoadSettings(); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs index bfcb47f428..dd2ae9b1cb 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs @@ -15,4 +15,6 @@ internal sealed class Icons internal static IconInfo CloseWindow { get; } = new IconInfo("\uE894"); // Clear internal static IconInfo Info { get; } = new IconInfo("\uE946"); // Info + + internal static IconInfo GenericAppIcon { get; } = new("\uE737"); // Favicon } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs index ecb09c8c38..1a8c5106d4 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs @@ -401,5 +401,23 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties { return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture); } } + + /// + /// Looks up a localized string similar to Use window icons. + /// + public static string windowwalker_SettingUseWindowIcon { + get { + return ResourceManager.GetString("windowwalker_SettingUseWindowIcon", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show the actual window icon instead of the process icon. + /// + public static string windowwalker_SettingUseWindowIcon_Description { + get { + return ResourceManager.GetString("windowwalker_SettingUseWindowIcon_Description", resourceCulture); + } + } } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx index c610b7b09c..3d61936a1d 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx @@ -235,4 +235,10 @@ No open windows found + + Use window icons + + + Show the actual window icon instead of the process icon + \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs index f3e35e8af3..97bbae741f 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs @@ -64,7 +64,7 @@ public class TerminalQuery : ITerminalQuery public IEnumerable GetTerminals() { var user = WindowsIdentity.GetCurrent().User; - var localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA"); + var localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); foreach (var p in _packageManager.FindPackagesForUser(user.Value).Where(p => Packages.Contains(p.Id.Name))) { diff --git a/src/modules/imageresizer/ui/ImageResizerUI.csproj b/src/modules/imageresizer/ui/ImageResizerUI.csproj index aca9f9d81e..b146db8435 100644 --- a/src/modules/imageresizer/ui/ImageResizerUI.csproj +++ b/src/modules/imageresizer/ui/ImageResizerUI.csproj @@ -24,6 +24,14 @@ Resources\ImageResizer.ico + + ImageResizerUI.dev.manifest + + + + ImageResizerUI.prod.manifest + + PublicResXFileCodeGenerator @@ -55,4 +63,14 @@ Resources.resx + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/ImageResizerUI.dev.manifest b/src/modules/imageresizer/ui/ImageResizerUI.dev.manifest new file mode 100644 index 0000000000..cb91bc2b66 --- /dev/null +++ b/src/modules/imageresizer/ui/ImageResizerUI.dev.manifest @@ -0,0 +1,8 @@ + + + + + diff --git a/src/modules/imageresizer/ui/ImageResizerUI.prod.manifest b/src/modules/imageresizer/ui/ImageResizerUI.prod.manifest new file mode 100644 index 0000000000..bbb50a9ec5 --- /dev/null +++ b/src/modules/imageresizer/ui/ImageResizerUI.prod.manifest @@ -0,0 +1,8 @@ + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml index 1228911082..9c820ba12c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml @@ -10,10 +10,10 @@ - + - - + + @@ -33,6 +33,7 @@ HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" Background="{TemplateBinding Background}" + BackgroundSizing="{TemplateBinding BackgroundSizing}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> @@ -145,7 +146,6 @@ x:Key="AccentKeyVisualStyle" BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="local:KeyVisual"> - @@ -161,6 +161,7 @@ VerticalAlignment="{TemplateBinding VerticalAlignment}" AutomationProperties.AccessibilityView="Raw" Background="{TemplateBinding Background}" + BackgroundSizing="{TemplateBinding BackgroundSizing}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml index b7983585ac..a747d71ef0 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml @@ -21,7 +21,7 @@ HorizontalAlignment="Right" Click="OpenDialogButton_Click" Style="{StaticResource SubtleButtonStyle}"> - + @@ -57,24 +57,28 @@ CornerRadius="{StaticResource ControlCornerRadius}" Orientation="Horizontal" Spacing="8"> - - + + Text="" + Visibility="Collapsed" /> @@ -84,6 +88,7 @@ + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml index 31e2f742e6..68ce9ffbff 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml @@ -231,7 +231,7 @@ Padding="20,16" AutomationProperties.AccessibilityView="Raw" Content="{Binding}" - CornerRadius="{StaticResource ControlCornerRadius}" + CornerRadius="{StaticResource OverlayCornerRadius}" FontSize="16" FontWeight="SemiBold" IsTabStop="False" diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 06647dc933..8384be49a1 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -5173,7 +5173,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Configure shortcut - Configure shortcut + Assign shortcut Quick access diff --git a/tools/build/build-installer.ps1 b/tools/build/build-installer.ps1 index f601146577..83a47f2092 100644 --- a/tools/build/build-installer.ps1 +++ b/tools/build/build-installer.ps1 @@ -21,12 +21,9 @@ Specifies the build configuration (e.g., 'Debug', 'Release'). Default is 'Releas .PARAMETER PerUser Specifies whether to build a per-user installer (true) or machine-wide installer (false). Default is true (per-user). -.PARAMETER InstallerSuffix -Specifies the suffix for the installer naming (e.g., 'wix5', 'vnext'). Default is 'wix5'. - .EXAMPLE .\build-installer.ps1 -Runs the installer build pipeline for x64 Release with default suffix (wix5). +Runs the installer build pipeline for x64 Release. .EXAMPLE .\build-installer.ps1 -Platform x64 -Configuration Release @@ -36,10 +33,6 @@ Runs the pipeline for x64 Release. .\build-installer.ps1 -Platform x64 -Configuration Release -PerUser false Runs the pipeline for x64 Release with machine-wide installer. -.EXAMPLE -.\build-installer.ps1 -Platform x64 -Configuration Release -InstallerSuffix vnext -Runs the pipeline for x64 Release with 'vnext' suffix. - .NOTES - Make sure to run this script from a Developer PowerShell (e.g., VS2022 Developer PowerShell). - Generated MSIX files will be signed using cert-sign-package.ps1. @@ -54,8 +47,7 @@ Runs the pipeline for x64 Release with 'vnext' suffix. param ( [string]$Platform = '', [string]$Configuration = 'Release', - [string]$PerUser = 'true', - [string]$InstallerSuffix = 'wix5' + [string]$PerUser = 'true' ) # Ensure helpers are available @@ -97,7 +89,7 @@ if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) { } Write-Host "PowerToys repository root detected: $repoRoot" -# WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; a separate WiX 3 installation is not required here. +# WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; no separate WiX installation is required. Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1} PerUser={2}" -f $Platform, $Configuration, $PerUser) Write-Host '' @@ -151,8 +143,8 @@ try { RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration -RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration +RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser" $Platform $Configuration -RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration +RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser" $Platform $Configuration Write-Host '[PIPELINE] Completed' diff --git a/tools/build/ensure-wix.ps1 b/tools/build/ensure-wix.ps1 deleted file mode 100644 index 988d382f07..0000000000 --- a/tools/build/ensure-wix.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -<# -.SYNOPSIS - Ensure WiX Toolset 3.14 (build 3141) is installed and ready to use. - -.DESCRIPTION - - Skips installation if the toolset is already installed (unless -Force is used). - - Otherwise downloads the official installer and binaries, verifies SHA-256, installs silently, - and copies wix.targets into the installation directory. -.PARAMETER Force - Forces reinstallation even if the toolset is already detected. -.PARAMETER InstallDir - The target installation path. Default is 'C:\Program Files (x86)\WiX Toolset v3.14'. -.EXAMPLE - .\EnsureWix.ps1 # Ensure WiX is installed - .\EnsureWix.ps1 -Force # Force reinstall -#> -[CmdletBinding()] -param( - [switch]$Force, - [string]$InstallDir = 'C:\Program Files (x86)\WiX Toolset v3.14' -) - -$ErrorActionPreference = 'Stop' -$ProgressPreference = 'SilentlyContinue' - -# Download URLs and expected SHA-256 hashes -$WixDownloadUrl = 'https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe' -$WixBinariesDownloadUrl = 'https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip' -$InstallerHashExpected = '6BF6D03D6923D9EF827AE1D943B90B42B8EBB1B0F68EF6D55F868FA34C738A29' -$BinariesHashExpected = '6AC824E1642D6F7277D0ED7EA09411A508F6116BA6FAE0AA5F2C7DAA2FF43D31' - -# Check if WiX is already installed -$candlePath = Join-Path $InstallDir 'bin\candle.exe' -if (-not $Force -and (Test-Path $candlePath)) { - Write-Host "WiX Toolset is already installed at `"$InstallDir`". Skipping installation." - return -} - -# Temp file paths -$tmpDir = [IO.Path]::GetTempPath() -$installer = Join-Path $tmpDir 'wix314.exe' -$binariesZip = Join-Path $tmpDir 'wix314-binaries.zip' - -# Download installer and binaries -Write-Host 'Downloading WiX installer...' -Invoke-WebRequest -Uri $WixDownloadUrl -OutFile $installer -UseBasicParsing -Write-Host 'Downloading WiX binaries...' -Invoke-WebRequest -Uri $WixBinariesDownloadUrl -OutFile $binariesZip -UseBasicParsing - -# Verify SHA-256 hashes -Write-Host 'Verifying installer hash...' -if ((Get-FileHash -Algorithm SHA256 $installer).Hash -ne $InstallerHashExpected) { - throw 'wix314.exe SHA256 hash mismatch' -} -Write-Host 'Verifying binaries hash...' -if ((Get-FileHash -Algorithm SHA256 $binariesZip).Hash -ne $BinariesHashExpected) { - throw 'wix314-binaries.zip SHA256 hash mismatch' -} - -# Perform silent installation -Write-Host 'Installing WiX Toolset silently...' -Start-Process -FilePath $installer -ArgumentList '/install','/quiet' -Wait - -# Extract binaries and copy wix.targets -$expandDir = Join-Path $tmpDir 'wix-binaries' -if (Test-Path $expandDir) { Remove-Item $expandDir -Recurse -Force } -Expand-Archive -Path $binariesZip -DestinationPath $expandDir -Force -Copy-Item -Path (Join-Path $expandDir 'wix.targets') ` - -Destination (Join-Path $InstallDir 'wix.targets') -Force - -Write-Host "WiX Toolset has been successfully installed at: $InstallDir"