diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index 2ef0425846..19d01faa1d 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -56,6 +56,8 @@ YVU YVYU zipfolder CODEOWNERS +VNext +vnext # FONTS @@ -98,13 +100,25 @@ svgl altdown BUTTONUP +bafunctions +Baf +Bitness +BUILDARCHSHORT CTRLALTDEL Ctrls +CSilent +CBal +CREATEBAFUNCTIONS +CPrereq +dirutil +DUtil +Editbox EXSEL HOLDENTER HOLDESC HOLDSPACE HOLDBACKSPACE +IDIGNORE KBDLLHOOKSTRUCT keyevent LAlt @@ -116,12 +130,16 @@ LCONTROL LCtrl LEFTDOWN LEFTUP +locutil +logutil +msimg MBUTTON MBUTTONDBLCLK MBUTTONDOWN MBUTTONUP MIDDLEDOWN MIDDLEUP +memutil NCRBUTTONDBLCLK NCRBUTTONDOWN NCRBUTTONUP @@ -134,8 +152,18 @@ RCONTROL RCtrl RIGHTDOWN RIGHTUP +Richedit +rgwz +resrutil +srd +scz +shelutil +thmutil +uriutil VKTAB +wcautil winkey +wininet WMKEYDOWN WMKEYUP WMSYSKEYDOWN @@ -145,6 +173,7 @@ XBUTTONDBLCLK XBUTTONDOWN XBUTTONUP XDOWN +xmlutil # Prefix pcs diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ddb7b18844..de9c5a062b 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -400,6 +400,7 @@ DString DSVG DTo DUMMYUNIONNAME +dutil DVASPECT DVASPECTINFO DVD @@ -1789,6 +1790,7 @@ uncompilable UNCPRIORITY UNDNAME UNICODETEXT +unins uninstalls Uniquifies unitconverter diff --git a/.gitignore b/.gitignore index 66532cc074..a1a09282a8 100644 --- a/.gitignore +++ b/.gitignore @@ -350,6 +350,10 @@ src/common/Telemetry/*.etl # Generated installer file for Monaco source files. /installer/PowerToysSetup/MonacoSRC.wxs +/installer/PowerToysSetupVNext/MonacoSRC.wxs # MSBuildCache /MSBuildCacheLogs/ + +# PowerToysInstaller Build Temp Files +installer/*/*.wxs.bk diff --git a/.pipelines/ESRPSigning_installer.json b/.pipelines/ESRPSigning_installer.json index b20e2cdc82..cd96fb6f64 100644 --- a/.pipelines/ESRPSigning_installer.json +++ b/.pipelines/ESRPSigning_installer.json @@ -5,6 +5,8 @@ { "MatchedPath": [ "PowerToysSetupCustomActions.dll", + "PowerToysSetupCustomActionsVNext.dll", + "SilentFilesInUseBAFunction.dll", "PowerToys*Setup-*.exe", "PowerToys*Setup-*.msi" ], diff --git a/.pipelines/v2/release.yml b/.pipelines/v2/release.yml index d6c2177720..e13792d8d1 100644 --- a/.pipelines/v2/release.yml +++ b/.pipelines/v2/release.yml @@ -20,6 +20,11 @@ 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 @@ -104,7 +109,8 @@ extends: useManagedIdentity: $(SigningUseManagedIdentity) clientId: $(SigningOriginalClientId) # Have msbuild use the release nuget config profile - additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=${{ parameters.enableAOT }} + additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=${{ parameters.enableAOT }} /p:InstallerSuffix=${{ parameters.installerSuffix }} + installerSuffix: ${{ parameters.installerSuffix }} 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 948ef951c3..e345cdc3fa 100644 --- a/.pipelines/v2/templates/job-build-project.yml +++ b/.pipelines/v2/templates/job-build-project.yml @@ -62,6 +62,9 @@ parameters: - name: versionNumber type: string default: '0.0.1' + - name: installerSuffix + type: string + default: "wix5" - name: useLatestWinAppSDK type: boolean default: false @@ -476,6 +479,23 @@ jobs: additionalBuildOptions: ${{ parameters.additionalBuildOptions }} buildUserInstaller: true # NOTE: This is the distinction between the above and below rules + - template: steps-build-installer-vnext.yml + parameters: + codeSign: ${{ parameters.codeSign }} + signingIdentity: ${{ parameters.signingIdentity }} + versionNumber: ${{ parameters.versionNumber }} + additionalBuildOptions: ${{ parameters.additionalBuildOptions }} + installerSuffix: ${{ parameters.installerSuffix }} + + - template: steps-build-installer-vnext.yml + parameters: + codeSign: ${{ parameters.codeSign }} + 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. # Removes: # - All .pdb files from any static libs .libs (which were only used during linking) @@ -494,7 +514,9 @@ jobs: - task: CopyFiles@2 displayName: Stage Installers inputs: - contents: "**/PowerToys*Setup-*.exe" + contents: |- + **/PowerToys*Setup-*.exe + !**/PowerToysSetupVNext/obj/** flattenFolders: True targetFolder: $(JobOutputDirectory) @@ -510,26 +532,48 @@ jobs: - pwsh: |- $p = "$(JobOutputDirectory)\" - $userHash = ((Get-Item $p\PowerToysUserSetup*.exe | Get-FileHash).Hash); - $machineHash = ((Get-Item $p\PowerToysSetup*.exe | Get-FileHash).Hash); - $userPlat = "hash_user_$(BuildPlatform).txt"; - $machinePlat = "hash_machine_$(BuildPlatform).txt"; - $combinedUserPath = $p + $userPlat; - $combinedMachinePath = $p + $machinePlat; - - echo $p - - echo $userPlat - echo $userHash - echo $combinedUserPath - - echo $machinePlat - echo $machineHash - echo $combinedMachinePath - - $userHash | out-file -filepath $combinedUserPath - $machineHash | out-file -filepath $combinedMachinePath - displayName: Calculate file hashes + $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" } + + if ($userSetupFiles.Count -gt 0) { + $userHash = ($userSetupFiles[0] | Get-FileHash).Hash; + $userPlat = "hash_user_$(BuildPlatform).txt"; + $combinedUserPath = $p + $userPlat; + echo "Regular User: $userHash" + $userHash | out-file -filepath $combinedUserPath + } + + if ($machineSetupFiles.Count -gt 0) { + $machineHash = ($machineSetupFiles[0] | Get-FileHash).Hash; + $machinePlat = "hash_machine_$(BuildPlatform).txt"; + $combinedMachinePath = $p + $machinePlat; + echo "Regular 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 - pwsh: |- diff --git a/.pipelines/v2/templates/steps-build-installer-vnext.yml b/.pipelines/v2/templates/steps-build-installer-vnext.yml new file mode 100644 index 0000000000..0f7908fceb --- /dev/null +++ b/.pipelines/v2/templates/steps-build-installer-vnext.yml @@ -0,0 +1,208 @@ +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: '' + - name: installerSuffix + type: string + default: "wix5" + +steps: + # Install WiX 5.0.2 tools needed for VNext installer (matching project SDK) + - task: DotNetCoreCLI@2 + displayName: Install WiX 5.0.2 tools + inputs: + command: 'custom' + custom: 'tool' + arguments: 'install --global wix --version 5.0.2' + + - pwsh: |- + & git clean -xfd -e *exe -- .\installer\ + displayName: ${{replace(replace(parameters.buildUserInstaller,'True','πŸ‘€'),'False','πŸ’»')}} Clean installer to reduce cross-contamination + + - pwsh: |- + # Determine whether this is a per-user build + $IsPerUser = $${{ parameters.buildUserInstaller }} + + # Build slug used to locate the artifacts + $InstallerBuildSlug = if ($IsPerUser) { 'UserSetup' } else { 'MachineSetup' } + + # VNext bundle folder; base name intentionally omits the VNext suffix + $InstallerFolder = 'PowerToysSetupVNext' + if ($IsPerUser) { + $InstallerBasename = "PowerToysUserSetup-${{ parameters.versionNumber }}-${{ parameters.installerSuffix }}-$(BuildPlatform)" + } + else { + $InstallerBasename = "PowerToysSetup-${{ parameters.versionNumber }}-${{ parameters.installerSuffix }}-$(BuildPlatform)" + } + + # Export variables for downstream steps + 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" + Write-Host "##vso[task.setvariable variable=InstallerFolder]$InstallerFolder" + 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 PowerToysSetupCustomActionsVNext + inputs: + solution: "**/installer/PowerToysSetup.sln" + vsVersion: 17.0 + 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 }} + 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 PowerToysSetupCustomActionsVNext + signingIdentity: ${{ parameters.signingIdentity }} + inputs: + FolderPath: 'installer/PowerToysSetupCustomActionsVNext/$(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 VNext MSI + inputs: + solution: "**/installer/PowerToysSetup.sln" + vsVersion: 17.0 + msbuildArgs: >- + -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) + configuration: $(BuildConfiguration) + clean: false # don't undo our hard work above by deleting the CustomActions dll + msbuildArchitecture: x64 + maximumCpuCount: true + + - script: |- + wix msi decompile installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).msi -x $(build.sourcesdirectory)\extractedMsi + dir $(build.sourcesdirectory)\extractedMsi + displayName: "${{replace(replace(parameters.buildUserInstaller,'True','πŸ‘€'),'False','πŸ’»')}} WiX5: Extract and verify MSI" + + # 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 VNext MSI + signingIdentity: ${{ parameters.signingIdentity }} + inputs: + FolderPath: 'installer/$(InstallerFolder)/$(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 VNext Bootstrapper + inputs: + solution: "**/installer/PowerToysSetup.sln" + vsVersion: 17.0 + msbuildArgs: >- + -restore + /t:PowerToysBootstrapperVNext + /p:PerUser=${{parameters.buildUserInstaller}};CIBuild=true + /p:InstallerSuffix=${{ parameters.installerSuffix }} + /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: |- + wix burn detach installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).exe -engine installer\engine.exe + displayName: "${{replace(replace(parameters.buildUserInstaller,'True','πŸ‘€'),'False','πŸ’»')}} WiX5: 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: |- + wix burn reattach installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).exe -engine installer\engine.exe -o installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).exe + displayName: "${{replace(replace(parameters.buildUserInstaller,'True','πŸ‘€'),'False','πŸ’»')}} WiX5: Reattach Engine to Bundle" + + - template: steps-esrp-signing.yml + parameters: + displayName: ${{replace(replace(parameters.buildUserInstaller,'True','πŸ‘€'),'False','πŸ’»')}} Sign Final Bootstrapper + signingIdentity: ${{ parameters.signingIdentity }} + inputs: + FolderPath: 'installer/$(InstallerFolder)/$(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 a5270e7d75..649c927a64 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -108,6 +108,14 @@ + + + + + + + + diff --git a/doc/devdocs/core/installer.md b/doc/devdocs/core/installer.md index b4619e26cd..8e9f008f2e 100644 --- a/doc/devdocs/core/installer.md +++ b/doc/devdocs/core/installer.md @@ -1,6 +1,6 @@ # PowerToys Installer -## Installer Architecture (WiX 3) +## Installer Architecture (WiX 3/ 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 +- First builds `PowerToysSetupCustomActions` DLL and signs it, for WiX5 project, installer will build `PowerToysSetupCustomActionsVNext` DLL. - 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 @@ -135,6 +135,23 @@ If you prefer, you can alternatively build prerequisite projects for the install The resulting `PowerToysSetup.msi` installer will be available in the `installer\PowerToysSetup\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`. + +``` +git clean -xfd -e *exe -- .\installer\ +MSBuild -t:restore .\installer\PowerToysSetup.sln -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release +MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64" +MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64" +``` + ### Supported arguments for the .EXE Bootstrapper installer Head over to the wiki to see the [full list of supported installer arguments][installerArgWiki]. diff --git a/installer/PowerToysSetup.sln b/installer/PowerToysSetup.sln index 540ef43d23..47d0c56070 100644 --- a/installer/PowerToysSetup.sln +++ b/installer/PowerToysSetup.sln @@ -1,5 +1,4 @@ -ο»Ώ -Microsoft Visual Studio Solution File, Format Version 12.00 +ο»ΏMicrosoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32414.318 MinimumVisualStudioVersion = 10.0.40219.1 @@ -17,6 +16,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "..\src\common\ve EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "..\src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}" EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysInstallerVNext", "PowerToysSetupVNext\PowerToysInstallerVNext.wixproj", "{B6E94700-DF38-41F6-A3FD-18B69674AB1E}" +EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysBootstrapperVNext", "PowerToysSetupVNext\PowerToysBootstrapperVNext.wixproj", "{DA4E9744-80BE-424C-B0F5-AFD8757DB575}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActionsVNext", "PowerToysSetupCustomActionsVNext\PowerToysSetupCustomActionsVNext.vcxproj", "{B3A354B0-1E54-4B55-A962-FB5AF9330C19}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SilentFilesInUseBAFunction", "PowerToysSetupVNext\SilentFilesInUseBA\SilentFilesInUseBAFunction.vcxproj", "{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -78,6 +85,36 @@ Global {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.Build.0 = Release|ARM64 {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.ActiveCfg = Release|x64 {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.Build.0 = Release|x64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.Build.0 = Debug|ARM64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.ActiveCfg = Debug|x64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.Build.0 = Debug|x64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.ActiveCfg = Release|ARM64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.Build.0 = Release|ARM64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.ActiveCfg = Release|x64 + {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.Build.0 = Release|x64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.Build.0 = Debug|ARM64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.ActiveCfg = Debug|x64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.Build.0 = Debug|x64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.ActiveCfg = Release|ARM64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.Build.0 = Release|ARM64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.ActiveCfg = Release|x64 + {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.Build.0 = Release|x64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.ActiveCfg = Debug|x64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.Build.0 = Debug|x64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.ActiveCfg = Release|ARM64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.Build.0 = Release|ARM64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.ActiveCfg = Release|x64 + {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.Build.0 = Release|x64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.ActiveCfg = Debug|x64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.Build.0 = Debug|x64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.ActiveCfg = Release|ARM64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.Build.0 = Release|ARM64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.ActiveCfg = Release|x64 + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -85,4 +122,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B7A3DA30-D443-40FF-AC51-988AD41E3962} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj index f76d15b73a..6a28fbc896 100644 --- a/installer/PowerToysSetup/PowerToysInstaller.wixproj +++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj @@ -57,7 +57,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil 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 diff --git a/installer/PowerToysSetup/publish.cmd b/installer/PowerToysSetup/publish.cmd index c6113bf285..f61668cffe 100644 --- a/installer/PowerToysSetup/publish.cmd +++ b/installer/PowerToysSetup/publish.cmd @@ -14,4 +14,4 @@ msbuild !PTRoot!\src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewH 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 +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/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj index 09ed1ee31a..0974bddbf9 100644 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj @@ -81,7 +81,7 @@ 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 + Backing up original files and populating .NET and WPF Runtime dependencies for WiX3 based installer diff --git a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp new file mode 100644 index 0000000000..8c3ad76448 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp @@ -0,0 +1,1411 @@ +#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/PowerToysSetupCustomActionsVNext/CustomAction.def b/installer/PowerToysSetupCustomActionsVNext/CustomAction.def new file mode 100644 index 0000000000..39efc9ff70 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.def @@ -0,0 +1,35 @@ +LIBRARY "PowerToysSetupCustomActionsVNext" + +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 + \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj new file mode 100644 index 0000000000..db6f6e6392 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj @@ -0,0 +1,183 @@ +ο»Ώ + + + + + + + {B3A354B0-1E54-4B55-A962-FB5AF9330C19} + Win32Proj + PowerToysSetupCustomActionsVNext + PowerToysSetupCustomActionsVNext + + + + 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)..\PowerToysSetupVNext\AdvancedPaste.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\AdvancedPaste.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Awake.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Awake.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\BaseApplications.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\BaseApplications.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\CmdPal.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\CmdPal.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\ColorPicker.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Core.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Core.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\EnvironmentVariables.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\EnvironmentVariables.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\FileExplorerPreview.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\FileExplorerPreview.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\FileLocksmith.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\FileLocksmith.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Hosts.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Hosts.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\ImageResizer.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\ImageResizer.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\KeyboardManager.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\KeyboardManager.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\MouseWithoutBorders.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\MouseWithoutBorders.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\NewPlus.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\NewPlus.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Peek.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Peek.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\PowerRename.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\PowerRename.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Product.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Product.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\RegistryPreview.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\RegistryPreview.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Resources.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Resources.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Run.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Run.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Settings.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Settings.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\ShortcutGuide.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\ShortcutGuide.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Tools.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Tools.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\WinAppSDK.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\WinAppSDK.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\WinUI3Applications.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\WinUI3Applications.wxs.bk"""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Workspaces.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Workspaces.wxs.bk"""" + if not "$(NormalizedPerUserValue)" == "true" call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetupVNext\generateAllFileComponents.ps1 -platform $(Platform) + if "$(NormalizedPerUserValue)" == "true" call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetupVNext\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;%(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) + + + $(NUGET_PACKAGES)\wixtoolset.wcautil\5.0.2\build\native\v14\$(Platform);$(NUGET_PACKAGES)\wixtoolset.dutil\5.0.2\build\native\v14\$(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/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj.filters b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj.filters new file mode 100644 index 0000000000..f4cf974dc0 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj.filters @@ -0,0 +1,31 @@ +ο»Ώ + + + + + + + + + Telemetry + + + Telemetry + + + + + + + + + + + + {6e73ce5d-e715-4e7e-b796-c5d180b07ff2} + + + + + + \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActionsVNext/RcResource.h b/installer/PowerToysSetupCustomActionsVNext/RcResource.h new file mode 100644 index 0000000000..aabbb532bc --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/RcResource.h @@ -0,0 +1,63 @@ +#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/PowerToysSetupCustomActionsVNext/Resource.rc b/installer/PowerToysSetupCustomActionsVNext/Resource.rc new file mode 100644 index 0000000000..c5f90c330d --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/Resource.rc @@ -0,0 +1,96 @@ +// 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/PowerToysSetupCustomActionsVNext/packages.config b/installer/PowerToysSetupCustomActionsVNext/packages.config new file mode 100644 index 0000000000..926fbe5dbf --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/packages.config @@ -0,0 +1,6 @@ +ο»Ώ + + + + + \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActionsVNext/pch.cpp b/installer/PowerToysSetupCustomActionsVNext/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/pch.cpp @@ -0,0 +1,5 @@ +// 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/PowerToysSetupCustomActionsVNext/pch.h b/installer/PowerToysSetupCustomActionsVNext/pch.h new file mode 100644 index 0000000000..ebfdd0c258 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/pch.h @@ -0,0 +1,37 @@ +#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/PowerToysSetupCustomActionsVNext/resource.h b/installer/PowerToysSetupCustomActionsVNext/resource.h new file mode 100644 index 0000000000..0a9e468096 --- /dev/null +++ b/installer/PowerToysSetupCustomActionsVNext/resource.h @@ -0,0 +1,20 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Resource.rc + +#define FILE_DESCRIPTION "PowerToys Setup Custom Actions" +#define INTERNAL_NAME "PowerToysSetupCustomActionsVNext" +#define ORIGINAL_FILENAME "PowerToysSetupCustomActionsVNext.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/PowerToysSetupVNext/AdvancedPaste.wxs b/installer/PowerToysSetupVNext/AdvancedPaste.wxs new file mode 100644 index 0000000000..f7a01b29ab --- /dev/null +++ b/installer/PowerToysSetupVNext/AdvancedPaste.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Awake.wxs b/installer/PowerToysSetupVNext/Awake.wxs new file mode 100644 index 0000000000..6dbd86ed43 --- /dev/null +++ b/installer/PowerToysSetupVNext/Awake.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/BaseApplications.wxs b/installer/PowerToysSetupVNext/BaseApplications.wxs new file mode 100644 index 0000000000..1947cbf1f2 --- /dev/null +++ b/installer/PowerToysSetupVNext/BaseApplications.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/CmdPal.wxs b/installer/PowerToysSetupVNext/CmdPal.wxs new file mode 100644 index 0000000000..f05a1f2f35 --- /dev/null +++ b/installer/PowerToysSetupVNext/CmdPal.wxs @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/ColorPicker.wxs b/installer/PowerToysSetupVNext/ColorPicker.wxs new file mode 100644 index 0000000000..97deb6c499 --- /dev/null +++ b/installer/PowerToysSetupVNext/ColorPicker.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Common.wxi b/installer/PowerToysSetupVNext/Common.wxi new file mode 100644 index 0000000000..4bb4f5a1dc --- /dev/null +++ b/installer/PowerToysSetupVNext/Common.wxi @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Core.wxs b/installer/PowerToysSetupVNext/Core.wxs new file mode 100644 index 0000000000..d3f992d82e --- /dev/null +++ b/installer/PowerToysSetupVNext/Core.wxs @@ -0,0 +1,122 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/CustomDialogs/PTInstallDirDlg.wxs b/installer/PowerToysSetupVNext/CustomDialogs/PTInstallDirDlg.wxs new file mode 100644 index 0000000000..0f1af3a9e4 --- /dev/null +++ b/installer/PowerToysSetupVNext/CustomDialogs/PTInstallDirDlg.wxs @@ -0,0 +1,28 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/CustomDialogs/PTLicenseDlg.wxs b/installer/PowerToysSetupVNext/CustomDialogs/PTLicenseDlg.wxs new file mode 100644 index 0000000000..969de90bf7 --- /dev/null +++ b/installer/PowerToysSetupVNext/CustomDialogs/PTLicenseDlg.wxs @@ -0,0 +1,28 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/installer/PowerToysSetupVNext/CustomDialogs/WixUI_PTInstallDir.wxs b/installer/PowerToysSetupVNext/CustomDialogs/WixUI_PTInstallDir.wxs new file mode 100644 index 0000000000..d7666077c0 --- /dev/null +++ b/installer/PowerToysSetupVNext/CustomDialogs/WixUI_PTInstallDir.wxs @@ -0,0 +1,77 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Directory.Build.props b/installer/PowerToysSetupVNext/Directory.Build.props new file mode 100644 index 0000000000..505e3cf844 --- /dev/null +++ b/installer/PowerToysSetupVNext/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + obj\Installer\ + obj\Bootstrapper\ + + + $(BaseIntermediateOutputPath) + + diff --git a/installer/PowerToysSetupVNext/EnvironmentVariables.wxs b/installer/PowerToysSetupVNext/EnvironmentVariables.wxs new file mode 100644 index 0000000000..41762e73d0 --- /dev/null +++ b/installer/PowerToysSetupVNext/EnvironmentVariables.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/FileExplorerPreview.wxs b/installer/PowerToysSetupVNext/FileExplorerPreview.wxs new file mode 100644 index 0000000000..d2e3f3fd71 --- /dev/null +++ b/installer/PowerToysSetupVNext/FileExplorerPreview.wxs @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/FileLocksmith.wxs b/installer/PowerToysSetupVNext/FileLocksmith.wxs new file mode 100644 index 0000000000..9ed8d5e29a --- /dev/null +++ b/installer/PowerToysSetupVNext/FileLocksmith.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Hosts.wxs b/installer/PowerToysSetupVNext/Hosts.wxs new file mode 100644 index 0000000000..3fd55ddfd1 --- /dev/null +++ b/installer/PowerToysSetupVNext/Hosts.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/ImageResizer.wxs b/installer/PowerToysSetupVNext/ImageResizer.wxs new file mode 100644 index 0000000000..86566cc597 --- /dev/null +++ b/installer/PowerToysSetupVNext/ImageResizer.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Images/banner.png b/installer/PowerToysSetupVNext/Images/banner.png new file mode 100644 index 0000000000..25f878ee82 Binary files /dev/null and b/installer/PowerToysSetupVNext/Images/banner.png differ diff --git a/installer/PowerToysSetupVNext/Images/dialog.png b/installer/PowerToysSetupVNext/Images/dialog.png new file mode 100644 index 0000000000..b422990982 Binary files /dev/null and b/installer/PowerToysSetupVNext/Images/dialog.png differ diff --git a/installer/PowerToysSetupVNext/Images/logo.png b/installer/PowerToysSetupVNext/Images/logo.png new file mode 100644 index 0000000000..c9f9405405 Binary files /dev/null and b/installer/PowerToysSetupVNext/Images/logo.png differ diff --git a/installer/PowerToysSetupVNext/Images/logo150.png b/installer/PowerToysSetupVNext/Images/logo150.png new file mode 100644 index 0000000000..35e757d8e5 Binary files /dev/null and b/installer/PowerToysSetupVNext/Images/logo150.png differ diff --git a/installer/PowerToysSetupVNext/Images/logo44.png b/installer/PowerToysSetupVNext/Images/logo44.png new file mode 100644 index 0000000000..b931931dff Binary files /dev/null and b/installer/PowerToysSetupVNext/Images/logo44.png differ diff --git a/installer/PowerToysSetupVNext/KeyboardManager.wxs b/installer/PowerToysSetupVNext/KeyboardManager.wxs new file mode 100644 index 0000000000..9aa9fc9472 --- /dev/null +++ b/installer/PowerToysSetupVNext/KeyboardManager.wxs @@ -0,0 +1,55 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/MouseWithoutBorders.wxs b/installer/PowerToysSetupVNext/MouseWithoutBorders.wxs new file mode 100644 index 0000000000..9a0453f296 --- /dev/null +++ b/installer/PowerToysSetupVNext/MouseWithoutBorders.wxs @@ -0,0 +1,17 @@ +ο»Ώ + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/NewPlus.wxs b/installer/PowerToysSetupVNext/NewPlus.wxs new file mode 100644 index 0000000000..7061c01126 --- /dev/null +++ b/installer/PowerToysSetupVNext/NewPlus.wxs @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Peek.wxs b/installer/PowerToysSetupVNext/Peek.wxs new file mode 100644 index 0000000000..f7f99326ef --- /dev/null +++ b/installer/PowerToysSetupVNext/Peek.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/PowerRename.wxs b/installer/PowerToysSetupVNext/PowerRename.wxs new file mode 100644 index 0000000000..331dd4521f --- /dev/null +++ b/installer/PowerToysSetupVNext/PowerRename.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/PowerToys.wxs b/installer/PowerToysSetupVNext/PowerToys.wxs new file mode 100644 index 0000000000..19906089bf --- /dev/null +++ b/installer/PowerToysSetupVNext/PowerToys.wxs @@ -0,0 +1,64 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj b/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj new file mode 100644 index 0000000000..1a3a4a8cac --- /dev/null +++ b/installer/PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj @@ -0,0 +1,62 @@ + + + false + + + Version=$(Version);InstallerSuffix=$(InstallerSuffix) + PowerToysVNextBootstrapper + + + $(DefineConstants);PerUser=true + + + $(DefineConstants);PerUser=false + + + $(DefineConstants);CIBuild=true + + + $(DefineConstants);CIBuild=false + + + Release + x64 + arm64 + wix5 + PowerToysSetup-$(Version)-$(InstallerSuffix)-$(Platform) + Bundle + True + PowerToysSetup-$(Version)-$(InstallerSuffix)-$(Platform) + PowerToysUserSetup-$(Version)-$(InstallerSuffix)-$(Platform) + $(Platform)\$(Configuration)\MachineSetup + $(Platform)\$(Configuration)\UserSetup + $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\MachineSetup + $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\UserSetup + + + + + + + + + + + + + + + + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D} + SilentFilesInUseBAFunction + false + + + + + + <_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" /> + + + + \ No newline at end of file diff --git a/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj b/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj new file mode 100644 index 0000000000..0cb9118b91 --- /dev/null +++ b/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj @@ -0,0 +1,167 @@ + + + + false + + + Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion);InstallerSuffix=$(InstallerSuffix) + 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" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2" + + + + Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion);InstallerSuffix=$(InstallerSuffix) + 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" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2" + + + + 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 + + + + $(DefineConstants);PerUser=true + + + $(DefineConstants);PerUser=false + + + $(DefineConstants);CIBuild=true + + + $(DefineConstants);CIBuild=false + + + PowerToysVNextInstaller + + Release + $(Platform) + 3.10 + {b6e94700-df38-41f6-a3fd-18b69674ab1e} + 2.0 + wix5 + PowerToysSetup-$(Version)-$(InstallerSuffix)-$(Platform) + PowerToysUserSetup-$(Version)-$(InstallerSuffix)-$(Platform) + Package + True + + + + + ICE91 + 1026;1076 + + + $(Platform)\$(Configuration)\MachineSetup + $(Platform)\$(Configuration)\UserSetup + $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\MachineSetup + $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\UserSetup + ICE40 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PowerToysSetupCustomActionsVNext + {B3A354B0-1E54-4B55-A962-FB5AF9330C19} + 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)" /> + + + + \ No newline at end of file diff --git a/installer/PowerToysSetupVNext/Product.wxs b/installer/PowerToysSetupVNext/Product.wxs new file mode 100644 index 0000000000..e343897d5d --- /dev/null +++ b/installer/PowerToysSetupVNext/Product.wxs @@ -0,0 +1,279 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/RegistryPreview.wxs b/installer/PowerToysSetupVNext/RegistryPreview.wxs new file mode 100644 index 0000000000..e55cce9b1a --- /dev/null +++ b/installer/PowerToysSetupVNext/RegistryPreview.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Resources.wxs b/installer/PowerToysSetupVNext/Resources.wxs new file mode 100644 index 0000000000..7e62a34be9 --- /dev/null +++ b/installer/PowerToysSetupVNext/Resources.wxs @@ -0,0 +1,464 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/RtfTheme.xml b/installer/PowerToysSetupVNext/RtfTheme.xml new file mode 100644 index 0000000000..da875cc4c3 --- /dev/null +++ b/installer/PowerToysSetupVNext/RtfTheme.xml @@ -0,0 +1,117 @@ + + + + + + Segoe UI + Segoe UI + Segoe UI + Segoe UI + + + + + + + + + + + + + + + #(loc.InstallAcceptCheckbox) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #(loc.FailureHyperlinkLogText) + + + + + + + diff --git a/installer/PowerToysSetupVNext/Run.wxs b/installer/PowerToysSetupVNext/Run.wxs new file mode 100644 index 0000000000..cf7542b70d --- /dev/null +++ b/installer/PowerToysSetupVNext/Run.wxs @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Settings.wxs b/installer/PowerToysSetupVNext/Settings.wxs new file mode 100644 index 0000000000..f9e5312ea7 --- /dev/null +++ b/installer/PowerToysSetupVNext/Settings.wxs @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/ShortcutGuide.wxs b/installer/PowerToysSetupVNext/ShortcutGuide.wxs new file mode 100644 index 0000000000..37b1c7800b --- /dev/null +++ b/installer/PowerToysSetupVNext/ShortcutGuide.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj new file mode 100644 index 0000000000..dfa43efebe --- /dev/null +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj @@ -0,0 +1,95 @@ + + + + + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + x64 + + + Release + x64 + + + + + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D} + DynamicLibrary + Unicode + SilentFilesInUseBAFunction + bafunctions.def + 10.0 + + + + + + + DynamicLibrary + true + v143 + Unicode + + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib;wininet.lib;version.lib + + + + + + + native,Version=v0.0 + + win-x64;win-arm64 + + + + + + Create + precomp.h + + + + + + + + + + + + + + + + + + + version.lib;%(AdditionalDependencies) + bafunctions.def + + + + + diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp new file mode 100644 index 0000000000..9b9e5d570f --- /dev/null +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp @@ -0,0 +1,162 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" +#include "BalBaseBAFunctions.h" +#include "BalBaseBAFunctionsProc.h" + +class CSilentFilesInUseBAFunctions : public CBalBaseBAFunctions +{ +public: // IBootstrapperApplication + virtual STDMETHODIMP OnDetectBegin( + __in BOOL fCached, + __in BOOTSTRAPPER_REGISTRATION_TYPE registrationType, + __in DWORD cPackages, + __inout BOOL* pfCancel + ) + { + HRESULT hr = S_OK; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running detect begin BA function. fCached=%d, registrationType=%d, cPackages=%u, fCancel=%d", fCached, registrationType, cPackages, *pfCancel); + + LExit: + return hr; + } + +public: // IBAFunctions + virtual STDMETHODIMP OnPlanBegin( + __in DWORD cPackages, + __inout BOOL* pfCancel + ) + { + HRESULT hr = S_OK; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running plan begin BA function. cPackages=%u, fCancel=%d", cPackages, *pfCancel); + + //------------------------------------------------------------------------------------------------- + // YOUR CODE GOES HERE + // BalExitOnFailure(hr, "Change this message to represent real error handling."); + //------------------------------------------------------------------------------------------------- + + LExit: + return hr; + } + + virtual STDMETHODIMP OnExecuteBegin( + __in DWORD cExecutingPackages, + __inout BOOL* pfCancel + ) + { + HRESULT hr = S_OK; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running execute begin BA function. cExecutingPackages=%u, fCancel=%d", cExecutingPackages, *pfCancel); + + return hr; + } + + virtual STDMETHODIMP OnExecuteFilesInUse( + __in_z LPCWSTR wzPackageId, + __in DWORD cFiles, + __in_ecount_z(cFiles) LPCWSTR* rgwzFiles, + __in int nRecommendation, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE source, + __inout int* pResult + ) + { + HRESULT hr = S_OK; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION CALLED *** Running OnExecuteFilesInUse BA function. packageId=%ls, cFiles=%u, recommendation=%d", wzPackageId, cFiles, nRecommendation); + + // Log each file that's in use + for (DWORD i = 0; i < cFiles; i++) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** FILE IN USE [%u]: %ls", i, rgwzFiles[i]); + } + + /* + * Summary: Why we return IDIGNORE here + * + * - Goal: Keep behavior consistent with our previous WiX 3 installer to avoid "files in use / close apps" prompts and preserve silent installs (e.g., winget). + * - WiX 5 change: We can no longer suppress that dialog the same way. Combined with winget adding /silent, this BAFunction returns IDIGNORE to continue without prompts. + * - Main trigger: Win10-style context menu uses registry + DLL; Explorer/dllhost.exe (COM Surrogate) often holds locks. Killing them is disruptive; this is a pragmatic trade-off. + * - Trade-off: Some file replacements may defer until reboot (PendingFileRename), but installation remains non-interruptive. + * - Full fix: Rewrite a custom Bootstrapper Application if we need complete control over prompts and behavior. + * - Note: Even with this handler, a full-UI install (e.g., double-clicking the installer) can still show a FilesInUse dialog; this primarily targets silent installs. + */ + *pResult = IDIGNORE; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** BA FUNCTION RETURNING IDIGNORE - SILENTLY CONTINUING ***"); + + return hr; + } + + virtual STDMETHODIMP OnExecuteComplete( + __in HRESULT hrStatus, + __inout BOOL* pfCancel + ) + { + HRESULT hr = S_OK; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running execute complete BA function. hrStatus=0x%x, fCancel=%d", hrStatus, *pfCancel); + + return hr; + } + +public: + // + // Constructor - initialize member variables. + // + CSilentFilesInUseBAFunctions( + __in HMODULE hModule + ) : CBalBaseBAFunctions(hModule) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** BA FUNCTION CONSTRUCTOR *** CSilentFilesInUseBAFunctions created"); + } + + // + // Destructor - release member variables. + // + ~CSilentFilesInUseBAFunctions() + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** BA FUNCTION DESTRUCTOR *** CSilentFilesInUseBAFunctions destroyed"); + } +}; + + +HRESULT WINAPI CreateBAFunctions( + __in HMODULE hModule, + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs, + __inout BA_FUNCTIONS_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + CSilentFilesInUseBAFunctions* pBAFunctions = NULL; + + // First thing - log that we're being called + BalInitialize(pArgs->pEngine); + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CREATEBAFUNCTIONS CALLED *** BA Function DLL is being loaded!"); + + pBAFunctions = new CSilentFilesInUseBAFunctions(hModule); + ExitOnNull(pBAFunctions, hr, E_OUTOFMEMORY, "Failed to create new CSilentFilesInUseBAFunctions object."); + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CREATEBAFUNCTIONS *** Created CSilentFilesInUseBAFunctions object"); + + hr = pBAFunctions->OnCreate(pArgs->pEngine, pArgs->pCommand); + ExitOnFailure(hr, "Failed to call OnCreate CPrereqBaf."); + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CREATEBAFUNCTIONS *** OnCreate completed successfully"); + + pResults->pfnBAFunctionsProc = BalBaseBAFunctionsProc; + pResults->pvBAFunctionsProcContext = pBAFunctions; + pBAFunctions = NULL; + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CREATEBAFUNCTIONS SUCCESS *** BA Function system initialized"); + +LExit: + if (FAILED(hr)) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "*** CREATEBAFUNCTIONS FAILED *** hr=0x%x", hr); + } + ReleaseObject(pBAFunctions); + + return hr; +} diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/bafunctions.cpp b/installer/PowerToysSetupVNext/SilentFilesInUseBA/bafunctions.cpp new file mode 100644 index 0000000000..72dc1f54c0 --- /dev/null +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/bafunctions.cpp @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static HINSTANCE vhInstance = NULL; + +extern "C" BOOL WINAPI DllMain( + IN HINSTANCE hInstance, + IN DWORD dwReason, + IN LPVOID /* pvReserved */ + ) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + ::DisableThreadLibraryCalls(hInstance); + vhInstance = hInstance; + break; + + case DLL_PROCESS_DETACH: + vhInstance = NULL; + break; + } + + return TRUE; +} + +extern "C" HRESULT WINAPI BAFunctionsCreate( + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs, + __inout BA_FUNCTIONS_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + + // This is required to enable logging functions. + BalInitialize(pArgs->pEngine); + + hr = CreateBAFunctions(vhInstance, pArgs, pResults); + BalExitOnFailure(hr, "Failed to create BAFunctions interface."); + +LExit: + return hr; +} + +extern "C" void WINAPI BAFunctionsDestroy( + __in const BA_FUNCTIONS_DESTROY_ARGS* /*pArgs*/, + __inout BA_FUNCTIONS_DESTROY_RESULTS* /*pResults*/ + ) +{ + BalUninitialize(); +} diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/bafunctions.def b/installer/PowerToysSetupVNext/SilentFilesInUseBA/bafunctions.def new file mode 100644 index 0000000000..6e016dad07 --- /dev/null +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/bafunctions.def @@ -0,0 +1,6 @@ +; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +EXPORTS + BAFunctionsCreate + BAFunctionsDestroy diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/precomp.h b/installer/PowerToysSetupVNext/SilentFilesInUseBA/precomp.h new file mode 100644 index 0000000000..6f679095ae --- /dev/null +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/precomp.h @@ -0,0 +1,37 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#include + +#pragma warning(push) +#pragma warning(disable:4458) // declaration of 'xxx' hides class member +#include +#pragma warning(pop) + +#include +#include +#include +#include +#include +#include +#include + +// Standard WiX header files, include as required +#include "dutil.h" +#include "dictutil.h" +#include "fileutil.h" +#include "pathutil.h" +#include "strutil.h" +#include "regutil.h" + +#include "BootstrapperApplicationBase.h" + +#include "BAFunctions.h" +#include "IBAFunctions.h" + +HRESULT WINAPI CreateBAFunctions( + __in HMODULE hModule, + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs, + __inout BA_FUNCTIONS_CREATE_RESULTS* pResults + ); diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/resource.h b/installer/PowerToysSetupVNext/SilentFilesInUseBA/resource.h new file mode 100644 index 0000000000..149a8ff48a --- /dev/null +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/resource.h @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define IDC_STATIC -1 + + +// 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 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/installer/PowerToysSetupVNext/Tools.wxs b/installer/PowerToysSetupVNext/Tools.wxs new file mode 100644 index 0000000000..9e43e90916 --- /dev/null +++ b/installer/PowerToysSetupVNext/Tools.wxs @@ -0,0 +1,33 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/WebView2/MicrosoftEdgeWebview2Setup.exe b/installer/PowerToysSetupVNext/WebView2/MicrosoftEdgeWebview2Setup.exe new file mode 100644 index 0000000000..d95ba2e893 Binary files /dev/null and b/installer/PowerToysSetupVNext/WebView2/MicrosoftEdgeWebview2Setup.exe differ diff --git a/installer/PowerToysSetupVNext/WinAppSDK.wxs b/installer/PowerToysSetupVNext/WinAppSDK.wxs new file mode 100644 index 0000000000..00b8395735 --- /dev/null +++ b/installer/PowerToysSetupVNext/WinAppSDK.wxs @@ -0,0 +1,461 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/WinUI3Applications.wxs b/installer/PowerToysSetupVNext/WinUI3Applications.wxs new file mode 100644 index 0000000000..4c177b960a --- /dev/null +++ b/installer/PowerToysSetupVNext/WinUI3Applications.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/Workspaces.wxs b/installer/PowerToysSetupVNext/Workspaces.wxs new file mode 100644 index 0000000000..86825ac393 --- /dev/null +++ b/installer/PowerToysSetupVNext/Workspaces.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 new file mode 100644 index 0000000000..fb63868f93 --- /dev/null +++ b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 @@ -0,0 +1,323 @@ +[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/PowerToysSetupVNext/generateMonacoWxs.ps1 b/installer/PowerToysSetupVNext/generateMonacoWxs.ps1 new file mode 100644 index 0000000000..da2db6ae80 --- /dev/null +++ b/installer/PowerToysSetupVNext/generateMonacoWxs.ps1 @@ -0,0 +1,97 @@ +[CmdletBinding()] +Param( + [Parameter(Mandatory = $True, Position = 1)] + [string]$monacoWxsFile, + [Parameter(Mandatory = $True, Position = 2)] + [string]$platform, + [Parameter(Mandatory = $True, Position = 3)] + [string]$nugetHeatPath +) + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +if ($platform -eq "x64") { + $HeatPath = Join-Path $nugetHeatPath "tools\net472\x64" +} else { + $HeatPath = Join-Path $nugetHeatPath "tools\net472\x86" +} + +# Validate heat.exe exists at the resolved path; fail fast if not found. +$heatExe = Join-Path $HeatPath "heat.exe" +if (-not (Test-Path $heatExe)) { + Write-Error "heat.exe not found at '$heatExe'. Ensure the WixToolset.Heat package (5.0.2) is restored under '$nugetHeatPath'." + exit 1 +} + +$SourceDir = Join-Path $scriptDir "..\..\src\Monaco\monacoSRC" # Now relative to script location +$OutputFile = Join-Path $scriptDir "MonacoSRC.wxs" +$ComponentGroup = "MonacoSRCHeatGenerated" +$DirectoryRef = "MonacoPreviewHandlerMonacoSRCFolder" +$Variable = "var.MonacoSRCHarvestPath" + +& $heatExe dir "$SourceDir" -out "$OutputFile" -cg "$ComponentGroup" -dr "$DirectoryRef" -var "$Variable" -gg -srd -nologo + +$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/PowerToysSetupVNext/packages.config b/installer/PowerToysSetupVNext/packages.config new file mode 100644 index 0000000000..569e1bea86 --- /dev/null +++ b/installer/PowerToysSetupVNext/packages.config @@ -0,0 +1,3 @@ +ο»Ώ + + diff --git a/installer/PowerToysSetupVNext/publish.cmd b/installer/PowerToysSetupVNext/publish.cmd new file mode 100644 index 0000000000..f61668cffe --- /dev/null +++ b/installer/PowerToysSetupVNext/publish.cmd @@ -0,0 +1,17 @@ +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/PowerToysSetupVNext/terminate_powertoys.cmd b/installer/PowerToysSetupVNext/terminate_powertoys.cmd new file mode 100644 index 0000000000..8206b90aae --- /dev/null +++ b/installer/PowerToysSetupVNext/terminate_powertoys.cmd @@ -0,0 +1,12 @@ +@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/tools/Verification scripts/verify-installation-script.ps1 b/tools/Verification scripts/verify-installation-script.ps1 new file mode 100644 index 0000000000..d617aca9a7 --- /dev/null +++ b/tools/Verification scripts/verify-installation-script.ps1 @@ -0,0 +1,831 @@ +#Requires -Version 5.1 + +<# +.SYNOPSIS + Verifies a PowerToys installation by checking all components, registry entries, files, and custom logic. + +.DESCRIPTION + This script comprehensively verifies a PowerToys installation by checking: + - Registry entries for both per-machine and per-user installations + - File and folder structure integrity + - Module registration and functionality + - WiX installer logic verification + - Custom action results + - DSC module installation + - Command Palette packages + +.PARAMETER InstallScope + Specifies the installation scope to verify. Valid values are 'PerMachine' or 'PerUser'. + Default is 'PerMachine'. + +.PARAMETER InstallPath + Optional. Specifies a custom installation path to verify. If not provided, the script will + detect the installation path from the registry. + +.EXAMPLE + .\verify-installation-script.ps1 -InstallScope PerMachine + +.EXAMPLE + .\verify-installation-script.ps1 -InstallScope PerUser + +.NOTES + Author: PowerToys Team + Requires: PowerShell 5.1 or later + Requires: Administrative privileges for per-machine verification +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $false)] + [ValidateSet('PerMachine', 'PerUser')] + [string]$InstallScope = 'PerMachine', + + [Parameter(Mandatory = $false)] + [string]$InstallPath +) + +# Initialize results tracking +$script:Results = @{ + Summary = @{ + TotalChecks = 0 + PassedChecks = 0 + FailedChecks = 0 + WarningChecks = 0 + OverallStatus = "Unknown" + } + Details = @{} + Timestamp = Get-Date + Computer = $env:COMPUTERNAME + User = $env:USERNAME + PowerShellVersion = $PSVersionTable.PSVersion.ToString() +} + +# PowerToys constants +$PowerToysUpgradeCodePerMachine = "{42B84BF7-5FBF-473B-9C8B-049DC16F7708}" +$PowerToysUpgradeCodePerUser = "{D8B559DB-4C98-487A-A33F-50A8EEE42726}" +$PowerToysRegistryKeyPerMachine = "HKLM:\SOFTWARE\Classes\PowerToys" +$PowerToysRegistryKeyPerUser = "HKCU:\SOFTWARE\Classes\PowerToys" + +# Utility functions +function Write-StatusMessage { + param( + [string]$Message, + [ValidateSet('Info', 'Success', 'Warning', 'Error')] + [string]$Level = 'Info' + ) + + $color = switch ($Level) { + 'Info' { 'White' } + 'Success' { 'Green' } + 'Warning' { 'Yellow' } + 'Error' { 'Red' } + } + + $prefix = switch ($Level) { + 'Info' { '[INFO]' } + 'Success' { '[PASS]' } + 'Warning' { '[WARN]' } + 'Error' { '[FAIL]' } + } + + Write-Host "$prefix $Message" -ForegroundColor $color +} + +function Add-CheckResult { + param( + [string]$Category, + [string]$CheckName, + [string]$Status, + [string]$Message, + [object]$Details = $null + ) + + $script:Results.Summary.TotalChecks++ + + switch ($Status) { + 'Pass' { $script:Results.Summary.PassedChecks++ } + 'Fail' { $script:Results.Summary.FailedChecks++ } + 'Warning' { $script:Results.Summary.WarningChecks++ } + } + + if (-not $script:Results.Details.ContainsKey($Category)) { + $script:Results.Details[$Category] = @{} + } + + $checkDetails = @{ + Status = $Status + Message = $Message + Details = $Details + Timestamp = Get-Date + } + + $script:Results.Details[$Category][$CheckName] = $checkDetails + + # Always show all checks with their status + $level = switch ($Status) { + 'Pass' { 'Success' } + 'Fail' { 'Error' } + 'Warning' { 'Warning' } + } + Write-StatusMessage "[$Category] $CheckName - $Message" -Level $level +} + +function Test-RegistryKey { + param( + [string]$Path + ) + try { + return Test-Path $Path + } + catch { + return $false + } +} + +function Get-RegistryValue { + param( + [string]$Path, + [string]$Name, + [object]$DefaultValue = $null + ) + try { + $value = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue + return $value.$Name + } + catch { + return $DefaultValue + } +} + +function Test-PowerToysInstallation { + param( + [ValidateSet('PerMachine', 'PerUser')] + [string]$Scope + ) + + Write-StatusMessage "Verifying PowerToys $Scope installation..." -Level Info + + # Determine registry paths based on scope + $registryKey = if ($Scope -eq 'PerMachine') { $PowerToysRegistryKeyPerMachine } else { $PowerToysRegistryKeyPerUser } + + # Check main registry key + $mainKeyExists = Test-RegistryKey -Path $registryKey + Add-CheckResult -Category "Registry" -CheckName "Main Registry Key ($Scope)" -Status $(if ($mainKeyExists) { 'Pass' } else { 'Fail' }) -Message "Registry key exists: $registryKey" + + if (-not $mainKeyExists) { + Add-CheckResult -Category "Installation" -CheckName "Installation Status ($Scope)" -Status 'Fail' -Message "PowerToys $Scope installation not found" + return $false + } + + # Check install scope value + $installScopeValue = Get-RegistryValue -Path $registryKey -Name "InstallScope" + $expectedScope = $Scope.ToLower() + if ($Scope -eq 'PerMachine') { $expectedScope = 'perMachine' } + if ($Scope -eq 'PerUser') { $expectedScope = 'perUser' } + + $scopeCorrect = $installScopeValue -eq $expectedScope + Add-CheckResult -Category "Registry" -CheckName "Install Scope Value ($Scope)" -Status $(if ($scopeCorrect) { 'Pass' } else { 'Fail' }) -Message "Install scope: Expected '$expectedScope', Found '$installScopeValue'" + + # Check for uninstall registry entry (this is what makes PowerToys appear in Add/Remove Programs) + $uninstallKey = if ($Scope -eq 'PerMachine') { + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" + } + else { + "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" + } + + try { + $powerToysEntry = Get-ItemProperty -Path $uninstallKey | Where-Object { + $_.DisplayName -like "*PowerToys*" + } | Select-Object -First 1 + + if ($powerToysEntry) { + Add-CheckResult -Category "Registry" -CheckName "Uninstall Registry Entry ($Scope)" -Status 'Pass' -Message "PowerToys uninstall entry found with DisplayName: $($powerToysEntry.DisplayName)" + + # Note: InstallLocation may or may not be set in the uninstall registry + # This is normal behavior as PowerToys uses direct file references for system bindings + if ($powerToysEntry.InstallLocation) { + Add-CheckResult -Category "Registry" -CheckName "Install Location Registry ($Scope)" -Status 'Pass' -Message "InstallLocation found: $($powerToysEntry.InstallLocation)" + } + # No need to report missing InstallLocation as it's not required + } + else { + Add-CheckResult -Category "Registry" -CheckName "Uninstall Registry Entry ($Scope)" -Status 'Fail' -Message "PowerToys uninstall entry not found in Windows uninstall registry" + } + } + catch { + Add-CheckResult -Category "Registry" -CheckName "Uninstall Registry Entry ($Scope)" -Status 'Fail' -Message "Failed to read Windows uninstall registry" + } + + # Check for installation folder + $installFolder = Get-PowerToysInstallPath -Scope $Scope + if ($installFolder -and (Test-Path $installFolder)) { + Add-CheckResult -Category "Installation" -CheckName "Install Folder ($Scope)" -Status 'Pass' -Message "Installation folder exists: $installFolder" + + # Verify core files + Test-CoreFiles -InstallPath $installFolder -Scope $Scope + + # Verify modules + Test-ModuleFiles -InstallPath $installFolder -Scope $Scope + + return $true + } + else { + Add-CheckResult -Category "Installation" -CheckName "Install Folder ($Scope)" -Status 'Fail' -Message "Installation folder not found or inaccessible: $installFolder" + return $false + } +} + +function Get-PowerToysInstallPath { + param( + [ValidateSet('PerMachine', 'PerUser')] + [string]$Scope + ) + + if ($InstallPath) { + return $InstallPath + } + + # Since InstallLocation may not be reliably set in the uninstall registry, + # we'll use the default installation paths based on scope + if ($Scope -eq 'PerMachine') { + $defaultPath = "${env:ProgramFiles}\PowerToys" + } + else { + $defaultPath = "${env:LOCALAPPDATA}\PowerToys" + } + + # Verify the path exists before returning it + if (Test-Path $defaultPath) { + return $defaultPath + } + + # If default path doesn't exist, try to get it from uninstall registry as fallback + $uninstallKey = if ($Scope -eq 'PerMachine') { + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" + } + else { + "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" + } + + try { + $powerToysEntry = Get-ItemProperty -Path $uninstallKey | Where-Object { + $_.DisplayName -like "*PowerToys*" + } | Select-Object -First 1 + + # Check for InstallLocation first, but it may not exist + if ($powerToysEntry -and $powerToysEntry.InstallLocation) { + return $powerToysEntry.InstallLocation.TrimEnd('\') + } + + # Check for UninstallString as alternative source of install path + if ($powerToysEntry -and $powerToysEntry.UninstallString) { + # Extract directory from uninstall string like "C:\Program Files\PowerToys\unins000.exe" + $uninstallExe = $powerToysEntry.UninstallString.Trim('"') + $installDir = Split-Path $uninstallExe -Parent + if ($installDir -and (Test-Path $installDir)) { + return $installDir + } + } + } + catch { + # If registry read fails, fall back to null + } + + # If we can't determine the install path, return null + return $null +} + +function Test-CoreFiles { + param( + [string]$InstallPath, + [string]$Scope + ) + + # Essential core files (must exist for basic functionality) + $essentialCoreFiles = @( + 'PowerToys.exe', + 'PowerToys.ActionRunner.exe', + 'License.rtf', + 'Notice.md' + ) + + # Critical signed PowerToys executable files (from ESRP signing config) + $criticalSignedFiles = @( + # Main PowerToys components + 'PowerToys.exe', + 'PowerToys.ActionRunner.exe', + 'PowerToys.Update.exe', + 'PowerToys.BackgroundActivatorDLL.dll', + 'PowerToys.FilePreviewCommon.dll', + 'PowerToys.Interop.dll', + + # Common libraries + 'CalculatorEngineCommon.dll', + 'PowerToys.ManagedTelemetry.dll', + 'PowerToys.ManagedCommon.dll', + 'PowerToys.ManagedCsWin32.dll', + 'PowerToys.Common.UI.dll', + 'PowerToys.Settings.UI.Lib.dll', + 'PowerToys.GPOWrapper.dll', + 'PowerToys.GPOWrapperProjection.dll', + 'PowerToys.AllExperiments.dll', + + # Module executables and libraries + 'PowerToys.AlwaysOnTop.exe', + 'PowerToys.AlwaysOnTopModuleInterface.dll', + 'PowerToys.CmdNotFoundModuleInterface.dll', + 'PowerToys.ColorPicker.dll', + 'PowerToys.ColorPickerUI.dll', + 'PowerToys.ColorPickerUI.exe', + 'PowerToys.CropAndLockModuleInterface.dll', + 'PowerToys.CropAndLock.exe', + 'PowerToys.PowerOCRModuleInterface.dll', + 'PowerToys.PowerOCR.dll', + 'PowerToys.PowerOCR.exe', + 'PowerToys.AdvancedPasteModuleInterface.dll', + 'PowerToys.AwakeModuleInterface.dll', + 'PowerToys.Awake.exe', + 'PowerToys.Awake.dll', + + # FancyZones + 'PowerToys.FancyZonesEditor.exe', + 'PowerToys.FancyZonesEditor.dll', + 'PowerToys.FancyZonesEditorCommon.dll', + 'PowerToys.FancyZonesModuleInterface.dll', + 'PowerToys.FancyZones.exe', + + # Preview handlers + 'PowerToys.GcodePreviewHandler.dll', + 'PowerToys.GcodePreviewHandler.exe', + 'PowerToys.GcodePreviewHandlerCpp.dll', + 'PowerToys.GcodeThumbnailProvider.dll', + 'PowerToys.GcodeThumbnailProvider.exe', + 'PowerToys.GcodeThumbnailProviderCpp.dll', + 'PowerToys.BgcodePreviewHandler.dll', + 'PowerToys.BgcodePreviewHandler.exe', + 'PowerToys.BgcodePreviewHandlerCpp.dll', + 'PowerToys.BgcodeThumbnailProvider.dll', + 'PowerToys.BgcodeThumbnailProvider.exe', + 'PowerToys.BgcodeThumbnailProviderCpp.dll', + 'PowerToys.MarkdownPreviewHandler.dll', + 'PowerToys.MarkdownPreviewHandler.exe', + 'PowerToys.MarkdownPreviewHandlerCpp.dll', + 'PowerToys.MonacoPreviewHandler.dll', + 'PowerToys.MonacoPreviewHandler.exe', + 'PowerToys.MonacoPreviewHandlerCpp.dll', + 'PowerToys.PdfPreviewHandler.dll', + 'PowerToys.PdfPreviewHandler.exe', + 'PowerToys.PdfPreviewHandlerCpp.dll', + 'PowerToys.PdfThumbnailProvider.dll', + 'PowerToys.PdfThumbnailProvider.exe', + 'PowerToys.PdfThumbnailProviderCpp.dll', + 'PowerToys.powerpreview.dll', + 'PowerToys.PreviewHandlerCommon.dll', + 'PowerToys.QoiPreviewHandler.dll', + 'PowerToys.QoiPreviewHandler.exe', + 'PowerToys.QoiPreviewHandlerCpp.dll', + 'PowerToys.QoiThumbnailProvider.dll', + 'PowerToys.QoiThumbnailProvider.exe', + 'PowerToys.QoiThumbnailProviderCpp.dll', + 'PowerToys.StlThumbnailProvider.dll', + 'PowerToys.StlThumbnailProvider.exe', + 'PowerToys.StlThumbnailProviderCpp.dll', + 'PowerToys.SvgPreviewHandler.dll', + 'PowerToys.SvgPreviewHandler.exe', + 'PowerToys.SvgPreviewHandlerCpp.dll', + 'PowerToys.SvgThumbnailProvider.dll', + 'PowerToys.SvgThumbnailProvider.exe', + 'PowerToys.SvgThumbnailProviderCpp.dll', + + # Image Resizer + 'PowerToys.ImageResizer.exe', + 'PowerToys.ImageResizer.dll', + 'PowerToys.ImageResizerExt.dll', + 'PowerToys.ImageResizerContextMenu.dll', + + # Keyboard Manager + 'PowerToys.KeyboardManager.dll', + 'PowerToys.KeyboardManagerEditorLibraryWrapper.dll', + + # PowerToys Run + 'PowerToys.Launcher.dll', + 'PowerToys.PowerLauncher.dll', + 'PowerToys.PowerLauncher.exe', + 'PowerToys.PowerLauncher.Telemetry.dll', + 'Wox.Infrastructure.dll', + 'Wox.Plugin.dll', + + # Mouse utilities + 'PowerToys.FindMyMouse.dll', + 'PowerToys.MouseHighlighter.dll', + 'PowerToys.MouseJump.dll', + 'PowerToys.MouseJump.Common.dll', + 'PowerToys.MousePointerCrosshairs.dll', + 'PowerToys.MouseJumpUI.dll', + 'PowerToys.MouseJumpUI.exe', + 'PowerToys.MouseWithoutBorders.dll', + 'PowerToys.MouseWithoutBorders.exe', + 'PowerToys.MouseWithoutBordersModuleInterface.dll', + 'PowerToys.MouseWithoutBordersService.dll', + 'PowerToys.MouseWithoutBordersService.exe', + 'PowerToys.MouseWithoutBordersHelper.dll', + 'PowerToys.MouseWithoutBordersHelper.exe', + + # PowerAccent + 'PowerAccent.Core.dll', + 'PowerToys.PowerAccent.dll', + 'PowerToys.PowerAccent.exe', + 'PowerToys.PowerAccentModuleInterface.dll', + 'PowerToys.PowerAccentKeyboardService.dll', + + # Workspaces + 'PowerToys.WorkspacesSnapshotTool.exe', + 'PowerToys.WorkspacesLauncher.exe', + 'PowerToys.WorkspacesWindowArranger.exe', + 'PowerToys.WorkspacesEditor.exe', + 'PowerToys.WorkspacesEditor.dll', + 'PowerToys.WorkspacesLauncherUI.exe', + 'PowerToys.WorkspacesLauncherUI.dll', + 'PowerToys.WorkspacesModuleInterface.dll', + 'PowerToys.WorkspacesCsharpLibrary.dll', + + # Shortcut Guide + 'PowerToys.ShortcutGuide.exe', + 'PowerToys.ShortcutGuideModuleInterface.dll', + + # ZoomIt + 'PowerToys.ZoomIt.exe', + 'PowerToys.ZoomItModuleInterface.dll', + 'PowerToys.ZoomItSettingsInterop.dll', + + # Command Palette + 'PowerToys.CmdPalModuleInterface.dll', + 'CmdPalKeyboardService.dll' + ) + + # WinUI3Apps signed files (in WinUI3Apps subdirectory) + $winUI3SignedFiles = @( + 'PowerToys.Settings.dll', + 'PowerToys.Settings.exe', + 'PowerToys.AdvancedPaste.exe', + 'PowerToys.AdvancedPaste.dll', + 'PowerToys.HostsModuleInterface.dll', + 'PowerToys.HostsUILib.dll', + 'PowerToys.Hosts.dll', + 'PowerToys.Hosts.exe', + 'PowerToys.FileLocksmithLib.Interop.dll', + 'PowerToys.FileLocksmithExt.dll', + 'PowerToys.FileLocksmithUI.exe', + 'PowerToys.FileLocksmithUI.dll', + 'PowerToys.FileLocksmithContextMenu.dll', + 'Peek.Common.dll', + 'Peek.FilePreviewer.dll', + 'Powertoys.Peek.UI.dll', + 'Powertoys.Peek.UI.exe', + 'Powertoys.Peek.dll', + 'PowerToys.EnvironmentVariablesModuleInterface.dll', + 'PowerToys.EnvironmentVariablesUILib.dll', + 'PowerToys.EnvironmentVariables.dll', + 'PowerToys.EnvironmentVariables.exe', + 'PowerToys.MeasureToolModuleInterface.dll', + 'PowerToys.MeasureToolCore.dll', + 'PowerToys.MeasureToolUI.dll', + 'PowerToys.MeasureToolUI.exe', + 'PowerToys.NewPlus.ShellExtension.dll', + 'PowerToys.NewPlus.ShellExtension.win10.dll', + 'PowerToys.PowerRenameExt.dll', + 'PowerToys.PowerRename.exe', + 'PowerToys.PowerRenameContextMenu.dll', + 'PowerToys.RegistryPreviewExt.dll', + 'PowerToys.RegistryPreviewUILib.dll', + 'PowerToys.RegistryPreview.dll', + 'PowerToys.RegistryPreview.exe' + ) + + # Tools signed files (in Tools subdirectory) + $toolsSignedFiles = @( + 'PowerToys.BugReportTool.exe' + ) + + # KeyboardManager signed files (in specific subdirectories) + $keyboardManagerFiles = @{ + 'KeyboardManagerEditor\PowerToys.KeyboardManagerEditor.exe' = 'KeyboardManagerEditor' + 'KeyboardManagerEngine\PowerToys.KeyboardManagerEngine.exe' = 'KeyboardManagerEngine' + } + + # Run plugins signed files (in RunPlugins subdirectories) + $runPluginFiles = @{ + 'RunPlugins\Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.dll' = 'Calculator plugin' + 'RunPlugins\Folder\Microsoft.Plugin.Folder.dll' = 'Folder plugin' + 'RunPlugins\Indexer\Microsoft.Plugin.Indexer.dll' = 'Indexer plugin' + 'RunPlugins\OneNote\Microsoft.PowerToys.Run.Plugin.OneNote.dll' = 'OneNote plugin' + 'RunPlugins\History\Microsoft.PowerToys.Run.Plugin.History.dll' = 'History plugin' + 'RunPlugins\PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.dll' = 'PowerToys plugin' + 'RunPlugins\Program\Microsoft.Plugin.Program.dll' = 'Program plugin' + 'RunPlugins\Registry\Microsoft.PowerToys.Run.Plugin.Registry.dll' = 'Registry plugin' + 'RunPlugins\WindowsSettings\Microsoft.PowerToys.Run.Plugin.WindowsSettings.dll' = 'Windows Settings plugin' + 'RunPlugins\Shell\Microsoft.Plugin.Shell.dll' = 'Shell plugin' + 'RunPlugins\Uri\Microsoft.Plugin.Uri.dll' = 'URI plugin' + 'RunPlugins\WindowWalker\Microsoft.Plugin.WindowWalker.dll' = 'Window Walker plugin' + 'RunPlugins\UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.dll' = 'Unit Converter plugin' + 'RunPlugins\VSCodeWorkspaces\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll' = 'VS Code Workspaces plugin' + 'RunPlugins\Service\Microsoft.PowerToys.Run.Plugin.Service.dll' = 'Service plugin' + 'RunPlugins\System\Microsoft.PowerToys.Run.Plugin.System.dll' = 'System plugin' + 'RunPlugins\TimeDate\Microsoft.PowerToys.Run.Plugin.TimeDate.dll' = 'Time Date plugin' + 'RunPlugins\ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.dll' = 'Value Generator plugin' + 'RunPlugins\WebSearch\Community.PowerToys.Run.Plugin.WebSearch.dll' = 'Web Search plugin' + 'RunPlugins\WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll' = 'Windows Terminal plugin' + } + + # Check essential core files (must exist) + Write-StatusMessage "Checking essential core files..." -Level Info + foreach ($file in $essentialCoreFiles) { + $filePath = Join-Path $InstallPath $file + $exists = Test-Path $filePath + Add-CheckResult -Category "Core Files" -CheckName "$file ($Scope)" -Status $(if ($exists) { 'Pass' } else { 'Fail' }) -Message "Essential file: $filePath" + } + + # Check critical signed files in root directory + Write-StatusMessage "Checking critical signed files in root directory..." -Level Info + foreach ($file in $criticalSignedFiles) { + $filePath = Join-Path $InstallPath $file + $exists = Test-Path $filePath + # Most signed files are critical, but some may be optional depending on configuration + $status = if ($exists) { 'Pass' } else { 'Warning' } + Add-CheckResult -Category "Signed Files" -CheckName "$file ($Scope)" -Status $status -Message "Signed file: $filePath" + } + + # Check WinUI3Apps signed files + Write-StatusMessage "Checking WinUI3Apps signed files..." -Level Info + foreach ($file in $winUI3SignedFiles) { + $filePath = Join-Path $InstallPath "WinUI3Apps\$file" + $exists = Test-Path $filePath + $status = if ($exists) { 'Pass' } else { 'Warning' } + Add-CheckResult -Category "Signed Files" -CheckName "WinUI3Apps\$file ($Scope)" -Status $status -Message "WinUI3 signed file: $filePath" + } + + # Check Tools signed files + Write-StatusMessage "Checking Tools signed files..." -Level Info + foreach ($file in $toolsSignedFiles) { + $filePath = Join-Path $InstallPath "Tools\$file" + $exists = Test-Path $filePath + $status = if ($exists) { 'Pass' } else { 'Warning' } + Add-CheckResult -Category "Signed Files" -CheckName "Tools\$file ($Scope)" -Status $status -Message "Tools signed file: $filePath" + } + + # Check KeyboardManager files + Write-StatusMessage "Checking KeyboardManager signed files..." -Level Info + foreach ($relativePath in $keyboardManagerFiles.Keys) { + $filePath = Join-Path $InstallPath $relativePath + $exists = Test-Path $filePath + $status = if ($exists) { 'Pass' } else { 'Warning' } + $description = $keyboardManagerFiles[$relativePath] + Add-CheckResult -Category "Signed Files" -CheckName "$relativePath ($Scope)" -Status $status -Message "KeyboardManager $description signed file: $filePath" + } + + # Check Run plugins files + Write-StatusMessage "Checking PowerToys Run plugin files..." -Level Info + foreach ($relativePath in $runPluginFiles.Keys) { + $filePath = Join-Path $InstallPath $relativePath + $exists = Test-Path $filePath + $status = if ($exists) { 'Pass' } else { 'Warning' } + $description = $runPluginFiles[$relativePath] + Add-CheckResult -Category "Signed Files" -CheckName "$relativePath ($Scope)" -Status $status -Message "PowerToys Run $description signed file: $filePath" + } +} + +function Test-ModuleFiles { + param( + [string]$InstallPath, + [string]$Scope + ) + + # PowerToys does not actually install modules in a "modules" subfolder. + # Instead, modules are integrated directly into the main installation or specific subfolders. + # Check for key module directories that should exist: + + # Check KeyboardManager components (installed as separate folders) + $keyboardManagerEditor = Join-Path $InstallPath "KeyboardManagerEditor" + $keyboardManagerEngine = Join-Path $InstallPath "KeyboardManagerEngine" + + if (Test-Path $keyboardManagerEditor) { + Add-CheckResult -Category "Modules" -CheckName "KeyboardManager Editor ($Scope)" -Status 'Pass' -Message "KeyboardManager Editor folder exists: $keyboardManagerEditor" + } + else { + Add-CheckResult -Category "Modules" -CheckName "KeyboardManager Editor ($Scope)" -Status 'Warning' -Message "KeyboardManager Editor folder not found: $keyboardManagerEditor" + } + + if (Test-Path $keyboardManagerEngine) { + Add-CheckResult -Category "Modules" -CheckName "KeyboardManager Engine ($Scope)" -Status 'Pass' -Message "KeyboardManager Engine folder exists: $keyboardManagerEngine" + } + else { + Add-CheckResult -Category "Modules" -CheckName "KeyboardManager Engine ($Scope)" -Status 'Warning' -Message "KeyboardManager Engine folder not found: $keyboardManagerEngine" + } + + # Check RunPlugins folder (contains PowerToys Run modules) + $runPluginsPath = Join-Path $InstallPath "RunPlugins" + if (Test-Path $runPluginsPath) { + Add-CheckResult -Category "Modules" -CheckName "Run Plugins Folder ($Scope)" -Status 'Pass' -Message "Run plugins folder exists: $runPluginsPath" + } + else { + Add-CheckResult -Category "Modules" -CheckName "Run Plugins Folder ($Scope)" -Status 'Warning' -Message "Run plugins folder not found: $runPluginsPath" + } + + # Check Tools folder + $toolsPath = Join-Path $InstallPath "Tools" + if (Test-Path $toolsPath) { + Add-CheckResult -Category "Modules" -CheckName "Tools Folder ($Scope)" -Status 'Pass' -Message "Tools folder exists: $toolsPath" + } + else { + Add-CheckResult -Category "Modules" -CheckName "Tools Folder ($Scope)" -Status 'Warning' -Message "Tools folder not found: $toolsPath" + } +} + +function Test-RegistryHandlers { + param( + [string]$Scope + ) + + $registryRoot = if ($Scope -eq 'PerMachine') { 'HKLM:' } else { 'HKCU:' } + # Test URL protocol handler + $protocolPath = "$registryRoot\SOFTWARE\Classes\powertoys" + if (Test-RegistryKey -Path $protocolPath) { + Add-CheckResult -Category "Registry Handlers" -CheckName "PowerToys URL Protocol ($Scope)" -Status 'Pass' -Message "URL protocol registered" + + # Check command handler + $commandPath = "$protocolPath\shell\open\command" + if (Test-RegistryKey -Path $commandPath) { + Add-CheckResult -Category "Registry Handlers" -CheckName "PowerToys URL Command ($Scope)" -Status 'Pass' -Message "URL command handler registered" + } + else { + Add-CheckResult -Category "Registry Handlers" -CheckName "PowerToys URL Command ($Scope)" -Status 'Fail' -Message "URL command handler not found" + } + } + else { + Add-CheckResult -Category "Registry Handlers" -CheckName "PowerToys URL Protocol ($Scope)" -Status 'Fail' -Message "URL protocol not registered" + } + + # Test CLSID registration for toast notifications + $toastClsidPath = "$registryRoot\SOFTWARE\Classes\CLSID\{DD5CACDA-7C2E-4997-A62A-04A597B58F76}" + if (Test-RegistryKey -Path $toastClsidPath) { + Add-CheckResult -Category "Registry Handlers" -CheckName "Toast Notification CLSID ($Scope)" -Status 'Pass' -Message "Toast notification CLSID registered" + } + else { + Add-CheckResult -Category "Registry Handlers" -CheckName "Toast Notification CLSID ($Scope)" -Status 'Warning' -Message "Toast notification CLSID not found" + } +} + +function Test-DSCModule { + param( + [string]$Scope + ) + + if ($Scope -eq 'PerUser') { + # For per-user installations, DSC module is installed via custom action to user's Documents + $userModulesPath = "$env:USERPROFILE\Documents\PowerShell\Modules\Microsoft.PowerToys.Configure" + if (Test-Path $userModulesPath) { + Add-CheckResult -Category "DSC Module" -CheckName "DSC Module (PerUser)" -Status 'Pass' -Message "DSC module found in user profile: $userModulesPath" + } + else { + Add-CheckResult -Category "DSC Module" -CheckName "DSC Module (PerUser)" -Status 'Fail' -Message "DSC module not found in user profile: $userModulesPath" + } + } + else { + # For per-machine installations, DSC module is installed to system WindowsPowerShell modules + $systemModulesPath = "${env:ProgramFiles}\WindowsPowerShell\Modules\Microsoft.PowerToys.Configure" + if (Test-Path $systemModulesPath) { + Add-CheckResult -Category "DSC Module" -CheckName "DSC Module (PerMachine)" -Status 'Pass' -Message "DSC module found in system modules: $systemModulesPath" + } + else { + Add-CheckResult -Category "DSC Module" -CheckName "DSC Module (PerMachine)" -Status 'Fail' -Message "DSC module not found in system modules: $systemModulesPath" + } + } +} + +function Test-CommandPalettePackages { + param( + [string]$InstallPath + ) + + $cmdPalPath = Join-Path $InstallPath "WinUI3Apps\CmdPal" + if (Test-Path $cmdPalPath) { + # Check for MSIX package file (the actual Command Palette installation) + $msixFiles = Get-ChildItem $cmdPalPath -Filter "*.msix" -ErrorAction SilentlyContinue + if ($msixFiles) { + Add-CheckResult -Category "Command Palette" -CheckName "CmdPal MSIX Package" -Status 'Pass' -Message "Found $($msixFiles.Count) Command Palette MSIX package(s)" + } + else { + Add-CheckResult -Category "Command Palette" -CheckName "CmdPal MSIX Package" -Status 'Warning' -Message "No Command Palette MSIX packages found" + } + } + else { + Add-CheckResult -Category "Command Palette" -CheckName "CmdPal Module" -Status 'Warning' -Message "Command Palette module not found at: $cmdPalPath" + } +} + +function Test-ContextMenuPackages { + param( + [string]$InstallPath + ) + + # Context menu packages are installed as sparse packages + # These MSIX packages should be present in the installation + $contextMenuPackages = @{ + "ImageResizerContextMenuPackage.msix" = @{ Name = "Image Resizer context menu package"; Location = "Root" } + "FileLocksmithContextMenuPackage.msix" = @{ Name = "File Locksmith context menu package"; Location = "WinUI3Apps" } + "PowerRenameContextMenuPackage.msix" = @{ Name = "PowerRename context menu package"; Location = "WinUI3Apps" } + "NewPlusPackage.msix" = @{ Name = "New+ context menu package"; Location = "WinUI3Apps" } + } + + # Check for packages based on their expected location + foreach ($packageFile in $contextMenuPackages.Keys) { + $packageInfo = $contextMenuPackages[$packageFile] + + if ($packageInfo.Location -eq "Root") { + $packagePath = Join-Path $InstallPath $packageFile + } + else { + $packagePath = Join-Path $InstallPath "WinUI3Apps\$packageFile" + } + + if (Test-Path $packagePath) { + Add-CheckResult -Category "Context Menu Packages" -CheckName $packageInfo.Name -Status 'Pass' -Message "Context menu package found: $packagePath" + } + else { + Add-CheckResult -Category "Context Menu Packages" -CheckName $packageInfo.Name -Status 'Fail' -Message "Context menu package not found: $packagePath" + } + } +} + +# Main execution +function Main { + Write-StatusMessage "Starting PowerToys Installation Verification" -Level Info + Write-StatusMessage "Scope: $InstallScope" -Level Info + + # Check the specified scope - no fallbacks, only what installer should create + $installationFound = $false + + if ($InstallScope -eq 'PerMachine') { + if (Test-PowerToysInstallation -Scope 'PerMachine') { + $installationFound = $true + Test-RegistryHandlers -Scope 'PerMachine' + Test-DSCModule -Scope 'PerMachine' + $installPath = Get-PowerToysInstallPath -Scope 'PerMachine' + if ($installPath) { + Test-CommandPalettePackages -InstallPath $installPath + Test-ContextMenuPackages -InstallPath $installPath + } + } + } + else { # PerUser + if (Test-PowerToysInstallation -Scope 'PerUser') { + $installationFound = $true + Test-RegistryHandlers -Scope 'PerUser' + Test-DSCModule -Scope 'PerUser' + $installPath = Get-PowerToysInstallPath -Scope 'PerUser' + if ($installPath) { + Test-CommandPalettePackages -InstallPath $installPath + Test-ContextMenuPackages -InstallPath $installPath + } + } + } + + if ($installationFound) { + # Common tests (only run if installation found) + # Note: Scheduled tasks are not created by installer, they're created at runtime + } + + # Calculate overall status + if ($script:Results.Summary.FailedChecks -eq 0) { + if ($script:Results.Summary.WarningChecks -eq 0) { + $script:Results.Summary.OverallStatus = "Healthy" + } + else { + $script:Results.Summary.OverallStatus = "Healthy with Warnings" + } + } + else { + $script:Results.Summary.OverallStatus = "Issues Detected" + } + + # Display summary + Write-Host "`n" -NoNewline + Write-StatusMessage "=== VERIFICATION SUMMARY ===" -Level Info + Write-StatusMessage "Total Checks: $($script:Results.Summary.TotalChecks)" -Level Info + Write-StatusMessage "Passed: $($script:Results.Summary.PassedChecks)" -Level Success + Write-StatusMessage "Failed: $($script:Results.Summary.FailedChecks)" -Level Error + Write-StatusMessage "Warnings: $($script:Results.Summary.WarningChecks)" -Level Warning + Write-StatusMessage "Overall Status: $($script:Results.Summary.OverallStatus)" -Level $( + switch ($script:Results.Summary.OverallStatus) { + 'Healthy' { 'Success' } + 'Healthy with Warnings' { 'Warning' } + default { 'Error' } + } + ) + + Write-StatusMessage "PowerToys Installation Verification Complete" -Level Info +} + +# Run the main function +Main diff --git a/tools/build/build-installer.ps1 b/tools/build/build-installer.ps1 index f185ddfb0f..a1a1f8d5f1 100644 --- a/tools/build/build-installer.ps1 +++ b/tools/build/build-installer.ps1 @@ -7,7 +7,7 @@ This script automates the end-to-end build and packaging process for PowerToys, - Restoring and building all necessary solutions (CmdPal, BugReportTool, etc.) - Cleaning up old output - Signing generated .msix packages -- Building the WiX-based MSI and bootstrapper installers +- Building the WiX v5 (VNext) MSI and bootstrapper installers It is designed to work in local development. The cert used to sign the packages is generated by @@ -21,9 +21,12 @@ 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 ARM64 Release (default). +Runs the installer build pipeline for x64 Release with default suffix (wix5). .EXAMPLE .\build-installer.ps1 -Platform x64 -Configuration Release @@ -33,12 +36,16 @@ 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. - This script will clean previous outputs under the build directories and installer directory (except *.exe files). - First time run need admin permission to trust the certificate. -- The built installer will be placed under: installer/PowerToysSetup/[Platform]/[Configuration]/User[Machine]Setup +- The built installer will be placed under: installer/PowerToysSetupVNext/[Platform]/[Configuration]/User[Machine]Setup relative to the solution root directory. - To run the full installation in other machines, call "./cert-management.ps1" to export the cert used to sign the packages. And trust the cert in the target machine. @@ -47,7 +54,8 @@ Runs the pipeline for x64 Release with machine-wide installer. param ( [string]$Platform = 'x64', [string]$Configuration = 'Release', - [string]$PerUser = 'true' + [string]$PerUser = 'true', + [string]$InstallerSuffix = 'wix5' ) # Find the PowerToys repository root automatically @@ -114,8 +122,7 @@ function RestoreThenBuild { RunMSBuild $Solution '/m' } -Write-Host ("Make sure wix is installed and available") -& (Join-Path $PSScriptRoot "ensure-wix.ps1") +# WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; a separate WiX 3 installation is not required here. Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1} PerUser={2}" -f $Platform, $Configuration, $PerUser) Write-Host '' @@ -154,8 +161,8 @@ try { RunMSBuild 'installer\PowerToysSetup.sln' '/t:restore /p:RestorePackagesConfig=true' -RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysInstaller /p:PerUser=$PerUser" +RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" -RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysBootstrapper /p:PerUser=$PerUser" +RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" Write-Host '[PIPELINE] Completed'