diff --git a/.gitignore b/.gitignore index 56dc4790..4d145b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ *.userosscache *.sln.docstates +# Thumbnails (macOS) +._* + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs @@ -186,7 +189,7 @@ publish/ *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted -*.pubxml +# *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..caaa65fe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "src/WingetCreateCore/Common/Msi/rust-msi"] + path = src/WingetCreateCore/Common/Msi/rust-msi + # TODO: Switch to mdsteele/rust-msi once the PR is merged + # https://github.com/mdsteele/rust-msi/pull/18 + url = https://github.com/vedantmgoyal9/rust-msi + shallow = true + branch = master diff --git a/.vsconfig b/.vsconfig index f0e492f6..e3cf6a66 100644 --- a/.vsconfig +++ b/.vsconfig @@ -2,6 +2,12 @@ "version": "1.0", "components": [ "Microsoft.VisualStudio.Workload.ManagedDesktop", - "Microsoft.VisualStudio.Workload.Universal" + "Microsoft.VisualStudio.Workload.Universal", + "Microsoft.VisualStudio.Component.Windows11SDK.26100", + "Microsoft.VisualStudio.Component.VC.Tools.ARM64", + "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC" + ], + "extensions": [ + "https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects" ] } diff --git a/README.md b/README.md index 60f9f88f..bbe74f7f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ - # Welcome to the Windows Package Manager Manifest Creator repository. -This repository contains the source code for the Windows Package Manager Manifest Creator. The Windows Package Manager Manifest Creator is designed to help generate or update manifest files for the [Community repo](https://github.com/microsoft/winget-pkgs). +This repository contains the source code for the Windows Package Manager Manifest Creator. The Windows Package Manager Manifest Creator is designed to help generate or update manifest files for the [Community repo](https://github.com/microsoft/winget-pkgs). ## Overview @@ -15,7 +14,7 @@ For your convenience, **WingetCreate** can be acquired a number of ways. ### Install from the github repo -The **Windows Package Manager Manifest Creator** is available for download from the [winget-create](https://github.com/microsoft/winget-create/releases) repository. To install the package, simply click the the MSIX file in your browser. Once it has downloaded, click open. +The **Windows Package Manager Manifest Creator** is available for download from the [winget-create](https://github.com/microsoft/winget-create/releases) repository. To install the package, simply click the the MSIX file in your browser. Once it has downloaded, click open. ### Install with Windows Package Manager @@ -43,19 +42,19 @@ choco install wingetcreate **WingetCreate** has the following commands: -| Command | Description | -| ------- | ----------- | -| [New](doc/new.md) | Command for creating a new manifest from scratch | -| [Update](doc/update.md) | Command for updating an existing manifest | -| [New-Locale](doc/new-locale.md) | Command for creating a new locale for an existing manifest | -| [Update-Locale](doc/update-locale.md) | Command for updating a locale for an existing manifest | -| [Submit](doc/submit.md) | Command for submitting an existing PR | -| [Show](doc/show.md) | Command for displaying existing manifests | -| [Token](doc/token.md) | Command for managing cached GitHub personal access tokens | -| [Settings](doc/settings.md) | Command for editing the settings file configurations | -| [Cache](doc/cache.md) | Command for managing downloaded installers stored in cache -| [Info](doc/info.md) | Displays information about the client | -| [-?](doc/help.md) | Displays command line help | +| Command | Description | +| ------------------------------------- | ---------------------------------------------------------- | +| [New](doc/new.md) | Command for creating a new manifest from scratch | +| [Update](doc/update.md) | Command for updating an existing manifest | +| [New-Locale](doc/new-locale.md) | Command for creating a new locale for an existing manifest | +| [Update-Locale](doc/update-locale.md) | Command for updating a locale for an existing manifest | +| [Submit](doc/submit.md) | Command for submitting an existing PR | +| [Show](doc/show.md) | Command for displaying existing manifests | +| [Token](doc/token.md) | Command for managing cached GitHub personal access tokens | +| [Settings](doc/settings.md) | Command for editing the settings file configurations | +| [Cache](doc/cache.md) | Command for managing downloaded installers stored in cache | +| [Info](doc/info.md) | Displays information about the client | +| [-?](doc/help.md) | Displays command line help | Click on the individual commands to learn more. @@ -72,22 +71,22 @@ You can also check out this [episode of Open at Microsoft](https://learn.microso ### Using the standalone exe: -The latest version of the standalone exe can be found at https://aka.ms/wingetcreate/latest, and the latest preview version can be found at https://aka.ms/wingetcreate/preview, both of these require [.NET Runtime 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) to be installed on the build machine. To install this on your build machine in your pipeline, you can include the following dotnet task: +The latest version of the standalone exe can be found at https://aka.ms/wingetcreate/latest, and the latest preview version can be found at https://aka.ms/wingetcreate/preview, both of these require [.NET Runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) to be installed on the build machine. To install this on your build machine in your pipeline, you can include the following dotnet task: ```yaml - - task: UseDotNet@2 - displayName: 'Install .NET Runtime' - inputs: - packageType: sdk - version: '6.x' - installationPath: '$(ProgramFiles)\dotnet' +- task: UseDotNet@2 + displayName: "Install .NET Runtime" + inputs: + packageType: sdk + version: "8.x" + installationPath: '$(ProgramFiles)\dotnet' ``` Or you can utilize a PowerShell task and run the following script. ```PowerShell Invoke-WebRequest https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1 - .\dotnet-install.ps1 -Runtime dotnet -Architecture x64 -Version 6.0.13 -InstallDir $env:ProgramFiles\dotnet + .\dotnet-install.ps1 -Runtime dotnet -Architecture x64 -Version 8 -InstallDir $env:ProgramFiles\dotnet ``` > [!IMPORTANT] @@ -109,14 +108,14 @@ Windows Server 2022 now supports App Execution Aliases, which means the alias `w ```yaml - powershell: | - # Download and install C++ Runtime framework package. - iwr https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile $(vcLibsBundleFile) - Add-AppxPackage $(vcLibsBundleFile) - - # Download Winget-Create msixbundle, install, and execute update. - iwr https://aka.ms/wingetcreate/latest/msixbundle -OutFile $(appxBundleFile) - Add-AppxPackage $(appxBundleFile) - wingetcreate update Microsoft.WingetCreate -u $(packageUrl) -v $(manifestVersion) -t $(GITHUB_PAT) --submit + # Download and install C++ Runtime framework package. + iwr https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile $(vcLibsBundleFile) + Add-AppxPackage $(vcLibsBundleFile) + + # Download Winget-Create msixbundle, install, and execute update. + iwr https://aka.ms/wingetcreate/latest/msixbundle -OutFile $(appxBundleFile) + Add-AppxPackage $(appxBundleFile) + wingetcreate update Microsoft.WingetCreate -u $(packageUrl) -v $(manifestVersion) -t $(GITHUB_PAT) --submit ``` The CLI also supports creating or updating manifests with multiple installer URLs. You can either create new manifests with multiple installer nodes using the [New Command](doc/new.md) or update existing manifests with multiple installer URLs using the [Update Command](doc/update.md). @@ -141,28 +140,30 @@ You can install the prerequisites in one of two ways: 1. Clone the repository 2. Configure your system - * Please use the [configuration file](.configurations/configuration.dsc.yaml). This can be applied by either: - * [Dev Home](https://github.com/microsoft/devhome)'s machine configuration tool - * WinGet configuration. If you have WinGet version [v1.6.2631 or later](https://github.com/microsoft/winget-cli/releases), run `winget configure .configurations/configuration.dsc.yaml` in an elevated shell from the project root so relative paths resolve correctly - * Alternatively, if you already are running the minimum OS version, have Visual Studio installed, and have developer mode enabled, you may configure your Visual Studio directly via the .vsconfig file. To do this: - * Open the Visual Studio Installer, select “More” on your product card and then "Import configuration" - * Specify the .vsconfig file at the root of the repo and select “Review Details” + - Please use the [configuration file](.configurations/configuration.dsc.yaml). This can be applied by either: + - [Dev Home](https://github.com/microsoft/devhome)'s machine configuration tool + - WinGet configuration. If you have WinGet version [v1.6.2631 or later](https://github.com/microsoft/winget-cli/releases), run `winget configure .configurations/configuration.dsc.yaml` in an elevated shell from the project root so relative paths resolve correctly + - Alternatively, if you already are running the minimum OS version, have Visual Studio installed, and have developer mode enabled, you may configure your Visual Studio directly via the .vsconfig file. To do this: + - Open the Visual Studio Installer, select “More” on your product card and then "Import configuration" + - Specify the .vsconfig file at the root of the repo and select “Review Details” #### Manual set up -* Windows 10 1709 (16299) or later -* [Developer mode enabled](https://docs.microsoft.com/windows/uwp/get-started/enable-your-device-for-development) (optional) -* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) - * Or use winget to install it ;) (although you may need to adjust the workloads via Tools -> Get Tools and Features...) -* [Git Large File Storage (LFS)](https://git-lfs.github.com/) -* The following workloads: - * .NET Desktop Development - * Universal Windows Platform Development -* Windows 11 SDK (10.0.22000.0) (Tools -> Get Tools and Features -> Individual Components) +- Windows 10 1709 (16299) or later +- [Developer mode enabled](https://docs.microsoft.com/windows/uwp/get-started/enable-your-device-for-development) +- [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) + - Or use winget to install it ;) (although you may need to adjust the workloads via Tools -> Get Tools and Features...) +- [Git Large File Storage (LFS)](https://git-lfs.github.com/) +- The following VS workloads, individual components and extensions: + - .NET Desktop Development (Workload) + - Windows application development (Workload) + - MSVC - VS2022 C++ ARM64/ARM64EC build tools (Latest) (Individual component) + - Windows 11 SDK (10.0.26100.0) (Individual component) + - Microsoft Visual Studio Installer Projects 2022 (Extension) ### Building -Open `winget-create\src\WingetCreateCLI.sln` in Visual Studio and build. We currently only build using the solution; command line methods of building a VS solution should work as well. +Open `winget-create\src\WingetCreateCLI.sln` in Visual Studio and build. We currently only build using the solution; command-line methods of building a VS solution should work as well. ## Testing the client @@ -172,24 +173,25 @@ Running unit and E2E tests are a great way to ensure that functionality is prese ### Testing Prerequisites -* Fork the [winget-pkgs-submission-test repository](https://github.com/microsoft/winget-pkgs-submission-test) -* Fill out the test parameters in the `WingetCreateTests/Test.runsettings` file - * `WingetPkgsTestRepoOwner`: The repository owner of the winget-pkgs-submission-test repo. (Repo owner must be forked from main "winget-pkgs-submission-test" repo) - * `WingetPkgsTestRepo`: The winget-pkgs test repository. (winget-pkgs-submission-test) - * `GitHubApiKey`: GitHub personal access token for testing. - * Instructions on [how to generate your own GitHubApiKey](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). - * Direct link to GitHub [Personal Access Tokens page](https://github.com/settings/tokens). - * `GitHubAppPrivateKey`: Leave blank, this is only used by the build server. +- Fork the [winget-pkgs-submission-test repository](https://github.com/microsoft/winget-pkgs-submission-test) +- Fill out the test parameters in the `WingetCreateTests/Test.runsettings` file + + - `WingetPkgsTestRepoOwner`: The repository owner of the winget-pkgs-submission-test repo. (Repo owner must be forked from main "winget-pkgs-submission-test" repo) + - `WingetPkgsTestRepo`: The winget-pkgs test repository. (winget-pkgs-submission-test) + - `GitHubApiKey`: GitHub personal access token for testing. + - Instructions on [how to generate your own GitHubApiKey](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). + - Direct link to GitHub [Personal Access Tokens page](https://github.com/settings/tokens). + - `GitHubAppPrivateKey`: Leave blank, this is only used by the build server. -* Set the solution wide runsettings file for the tests - * Go to `Test` menu > `Configure Run Settings` -> `Select Solution Wide runsettings File` -> Choose your configured runsettings file +- Set the solution-wide runsettings file for the tests + - Go to `Test` menu > `Configure Run Settings` -> `Select Solution Wide runsettings File` -> Choose your configured runsettings file > [!CAUTION] > You should treat your access token like a password. To avoid exposing your PAT, be sure to reset changes to the `WingetCreateTests/Test.runsettings` file before committing your changes. You can also use the command `git update-index --skip-worktree src/WingetCreateTests/WingetCreateTests/Test.runsettings` command to untrack changes to the file and prevent it from being committed. ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. More information is available in our [CONTRIBUTING.md](/CONTRIBUTING.md) file. diff --git a/pipelines/azure-pipelines.yml b/pipelines/azure-pipelines.yml index 435f195c..b830f56b 100644 --- a/pipelines/azure-pipelines.yml +++ b/pipelines/azure-pipelines.yml @@ -21,6 +21,9 @@ variables: # Appx Package Directory appxPackageDir: '$(Build.ArtifactStagingDirectory)\AppxPackages' + # Portable executables directory + portableExecutablesDir: '$(Build.ArtifactStagingDirectory)\PortableExecutables' + # WingetCreate Msix Bundle Directory wingetCreatePackageDir: '$(appxPackageDir)\WingetCreateMsixBundle' @@ -34,7 +37,7 @@ variables: buildPlatform: "x64" # Target framework - targetFramework: "net8.0-windows10.0.22000.0" + targetFramework: "net8.0-windows10.0.26100.0" resources: repositories: @@ -91,6 +94,7 @@ extends: steps: - checkout: self lfs: "true" + submodules: true - task: PowerShell@2 displayName: Display version and bundle path for diagnosing @@ -117,6 +121,17 @@ extends: nugetConfigPath: "NuGet.config" projects: $(workingDirectory)/**/*.csproj + - displayName: Build rust-msi + pwsh: | + rustup target add aarch64-pc-windows-msvc + cargo build --release --target x86_64-pc-windows-msvc + cargo build --release --target aarch64-pc-windows-msvc + $cmdstr = 'cargo zigbuild --release --target x86_64-unknown-linux-gnu && ' + + 'cargo zigbuild --release --target aarch64-unknown-linux-gnu && ' + + 'cargo zigbuild --target aarch64-apple-darwin' + docker run --rm -v ${pwd}:c:\io -w c:\io ghcr.io/rust-cross/cargo-zigbuild.windows:main cmd /c $cmdstr + workingDirectory: $(workingDirectory)/WingetCreateCore/Common/Msi/rust-msi + - task: MSBuild@1 displayName: Build Solution inputs: @@ -124,7 +139,6 @@ extends: solution: "$(solution)" configuration: "$(buildConfiguration)" msbuildArguments: '/p:AppxBundleOutput="$(appxBundlePath)" - /p:AppxBundle=Always /p:UapAppxPackageBuildMode=SideloadOnly /p:AppxPackageSigningEnabled=false' @@ -136,12 +150,66 @@ extends: runSettingsFile: 'src\WingetCreateTests\WingetCreateTests\Test.runsettings' overrideTestrunParameters: '-WingetPkgsTestRepoOwner microsoft -WingetPkgsTestRepo winget-pkgs-submission-test -GitHubAppPrivateKey "$(GitHubApp_PrivateKey)"' + - task: DotNetCoreCLI@2 + displayName: "Publish (win-x64)" + inputs: + command: "publish" + projects: "WingetCreateCLI.csproj" + arguments: "-c Release -r win-x64 --output ./output/win-x64" + workingDirectory: "$(workingDirectory)/WingetCreateCLI" + + - task: DotNetCoreCLI@2 + displayName: "Publish (win-arm64)" + inputs: + command: "publish" + projects: "WingetCreateCLI.csproj" + arguments: "-c Release -r win-arm64 --output ./output/win-arm64" + workingDirectory: "$(workingDirectory)/WingetCreateCLI" + + - task: DotNetCoreCLI@2 + displayName: "Publish (linux-x64)" + inputs: + command: "publish" + projects: "WingetCreateCLI.csproj" + arguments: "-c Release -r linux-x64 --output ./output/linux-x64" + workingDirectory: "$(workingDirectory)/WingetCreateCLI" + + - task: DotNetCoreCLI@2 + displayName: "Publish (linux-arm64)" + inputs: + command: "publish" + projects: "WingetCreateCLI.csproj" + arguments: "-c Release -r linux-arm64 --output ./output/linux-arm64" + workingDirectory: "$(workingDirectory)/WingetCreateCLI" + + - task: DotNetCoreCLI@2 + displayName: "Publish (osx-arm64)" + inputs: + command: "publish" + projects: "WingetCreateCLI.csproj" + arguments: "-c Release -r osx-arm64 --output ./output/osx-arm64" + workingDirectory: "$(workingDirectory)/WingetCreateCLI" + - task: 1ES.PublishPipelineArtifact@1 inputs: targetPath: $(wingetCreatePackageDir) artifactName: wingetcreate_msixbundle displayName: Publish WingetCreate msix bundle + - pwsh: | + @('win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-arm64') | ForEach-Object { + $wc = get-item ./$_/WingetCreateCLI$(if($_.StartsWith("win")){".exe"}) + move-item $wc.fullname $(portableExecutablesDir)\wingetcreate-$_$(if($_.StartsWith("win")){".exe"}) + } + move-item $(portableExecutablesDir)\wingetcreate-osx-arm64 $(portableExecutablesDir)\wingetcreate-macos-arm64 + workingDirectory: $(workingDirectory)/WingetCreateCLI/output + + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: $(portableExecutablesDir) + artifactName: wingetcreate_exes + displayName: Publish WingetCreate executables + - task: ComponentGovernanceComponentDetection@0 displayName: Component Governance inputs: diff --git a/src/WingetCreateCLI.sln b/src/WingetCreateCLI.sln index e7a47962..81c44d1f 100644 --- a/src/WingetCreateCLI.sln +++ b/src/WingetCreateCLI.sln @@ -1,101 +1,107 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30621.155 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateCLI", "WingetCreateCLI\WingetCreateCLI.csproj", "{C36B8829-36E2-44BB-942C-00F29FCAE973}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateCLI", "WingetCreateCLI\WingetCreateCLI.csproj", "{D5855B01-208D-4A6F-9F56-F38780A121EB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WingetCreateTests", "WingetCreateTests", "{AA9952DB-2343-4C89-8273-FD34F1DC6D87}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WingetCreateTests", "WingetCreateTests", "{2365F14C-C2A7-4BED-8EA1-EE290794E518}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateTestExeInstaller", "WingetCreateTests\WingetCreateTestExeInstaller\WingetCreateTestExeInstaller.csproj", "{D61EDBD2-637B-4B5A-848E-2B1A505BE883}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateTestExeInstaller", "WingetCreateTests\WingetCreateTestExeInstaller\WingetCreateTestExeInstaller.csproj", "{B7B1E135-E27C-4189-9C12-95BC387EF130}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateTests", "WingetCreateTests\WingetCreateTests\WingetCreateTests.csproj", "{90EB2100-7BDB-4FFC-B657-3A63EEED93F9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateCore", "WingetCreateCore\WingetCreateCore.csproj", "{5613C196-3D8B-4B5C-9288-B24B01DDA3BE}" +Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "WingetCreateTestMsiInstaller", "WingetCreateTests\WingetCreateTestMsiInstaller\WingetCreateTestMsiInstaller.vdproj", "{E396AC31-F60E-494A-BD4E-93A3C7E488AE}" EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "WingetCreateTestMsixInstaller", "WingetCreateTests\WingetCreateTestMsixInstaller\WingetCreateTestMsixInstaller.wapproj", "{154EB646-D902-41B5-9CE1-E78ACC63AA0A}" EndProject -Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "WingetCreatePackage", "WingetCreatePackage\WingetCreatePackage.wapproj", "{AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateTests", "WingetCreateTests\WingetCreateTests\WingetCreateTests.csproj", "{E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A1727DC5-E7A5-4899-BF53-EAF658055D61}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateCore", "WingetCreateCore\WingetCreateCore.csproj", "{710A0BDF-18CC-4534-A2CA-1BF049D59DE9}" +EndProject +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "WingetCreatePackage", "WingetCreatePackage\WingetCreatePackage.wapproj", "{AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Debug|x64.ActiveCfg = Debug|x64 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Debug|x64.Build.0 = Debug|x64 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Debug|x86.ActiveCfg = Debug|x86 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Debug|x86.Build.0 = Debug|x86 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Release|x64.ActiveCfg = Release|x64 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Release|x64.Build.0 = Release|x64 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Release|x86.ActiveCfg = Release|x86 - {C36B8829-36E2-44BB-942C-00F29FCAE973}.Release|x86.Build.0 = Release|x86 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Debug|x64.ActiveCfg = Debug|x64 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Debug|x64.Build.0 = Debug|x64 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Debug|x86.ActiveCfg = Debug|x86 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Debug|x86.Build.0 = Debug|x86 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Release|x64.ActiveCfg = Release|x64 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Release|x64.Build.0 = Release|x64 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Release|x86.ActiveCfg = Release|x86 - {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Release|x86.Build.0 = Release|x86 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Debug|x64.ActiveCfg = Debug|x64 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Debug|x64.Build.0 = Debug|x64 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Debug|x86.ActiveCfg = Debug|x86 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Debug|x86.Build.0 = Debug|x86 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Release|x64.ActiveCfg = Release|x64 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Release|x64.Build.0 = Release|x64 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Release|x86.ActiveCfg = Release|x86 - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Release|x86.Build.0 = Release|x86 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Debug|x64.ActiveCfg = Debug|x64 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Debug|x64.Build.0 = Debug|x64 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Debug|x86.ActiveCfg = Debug|x86 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Debug|x86.Build.0 = Debug|x86 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Release|x64.ActiveCfg = Release|x64 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Release|x64.Build.0 = Release|x64 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Release|x86.ActiveCfg = Release|x86 - {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Release|x86.Build.0 = Release|x86 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Debug|ARM64.Build.0 = Debug|ARM64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Debug|x64.ActiveCfg = Debug|x64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Debug|x64.Build.0 = Debug|x64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Release|ARM64.ActiveCfg = Release|ARM64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Release|ARM64.Build.0 = Release|ARM64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Release|x64.ActiveCfg = Release|x64 + {D5855B01-208D-4A6F-9F56-F38780A121EB}.Release|x64.Build.0 = Release|x64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Debug|ARM64.Build.0 = Debug|ARM64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Debug|x64.ActiveCfg = Debug|x64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Debug|x64.Build.0 = Debug|x64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Release|ARM64.ActiveCfg = Release|ARM64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Release|ARM64.Build.0 = Release|ARM64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Release|x64.ActiveCfg = Release|x64 + {B7B1E135-E27C-4189-9C12-95BC387EF130}.Release|x64.Build.0 = Release|x64 + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Debug|ARM64.ActiveCfg = Debug + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Debug|ARM64.Build.0 = Debug + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Debug|x64.ActiveCfg = Debug + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Debug|x64.Build.0 = Debug + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Release|ARM64.ActiveCfg = Release + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Release|ARM64.Build.0 = Release + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Release|x64.ActiveCfg = Release + {E396AC31-F60E-494A-BD4E-93A3C7E488AE}.Release|x64.Build.0 = Release + {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|ARM64.Build.0 = Debug|ARM64 + {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|ARM64.Deploy.0 = Debug|ARM64 {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|x64.ActiveCfg = Debug|x64 {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|x64.Build.0 = Debug|x64 {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|x64.Deploy.0 = Debug|x64 - {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|x86.ActiveCfg = Debug|x86 - {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|x86.Build.0 = Debug|x86 - {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Debug|x86.Deploy.0 = Debug|x86 + {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|ARM64.ActiveCfg = Release|ARM64 + {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|ARM64.Build.0 = Release|ARM64 + {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|ARM64.Deploy.0 = Release|ARM64 {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|x64.ActiveCfg = Release|x64 {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|x64.Build.0 = Release|x64 {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|x64.Deploy.0 = Release|x64 - {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|x86.ActiveCfg = Release|x86 - {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|x86.Build.0 = Release|x86 - {154EB646-D902-41B5-9CE1-E78ACC63AA0A}.Release|x86.Deploy.0 = Release|x86 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Debug|ARM64.Build.0 = Debug|ARM64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Debug|x64.ActiveCfg = Debug|x64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Debug|x64.Build.0 = Debug|x64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Release|ARM64.ActiveCfg = Release|ARM64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Release|ARM64.Build.0 = Release|ARM64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Release|x64.ActiveCfg = Release|x64 + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5}.Release|x64.Build.0 = Release|x64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Debug|ARM64.Build.0 = Debug|ARM64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Debug|x64.ActiveCfg = Debug|x64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Debug|x64.Build.0 = Debug|x64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Release|ARM64.ActiveCfg = Release|ARM64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Release|ARM64.Build.0 = Release|ARM64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Release|x64.ActiveCfg = Release|x64 + {710A0BDF-18CC-4534-A2CA-1BF049D59DE9}.Release|x64.Build.0 = Release|x64 + {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|ARM64.Build.0 = Debug|ARM64 + {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|ARM64.Deploy.0 = Debug|ARM64 {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|x64.ActiveCfg = Debug|x64 {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|x64.Build.0 = Debug|x64 {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|x64.Deploy.0 = Debug|x64 - {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|x86.ActiveCfg = Debug|x86 - {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|x86.Build.0 = Debug|x86 - {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Debug|x86.Deploy.0 = Debug|x86 + {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|ARM64.ActiveCfg = Release|ARM64 + {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|ARM64.Build.0 = Release|ARM64 + {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|ARM64.Deploy.0 = Release|ARM64 {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|x64.ActiveCfg = Release|x64 {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|x64.Build.0 = Release|x64 {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|x64.Deploy.0 = Release|x64 - {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|x86.ActiveCfg = Release|x86 - {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|x86.Build.0 = Release|x86 - {AC37DFD2-1332-4282-B373-8DCF8BB4E3BA}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {D61EDBD2-637B-4B5A-848E-2B1A505BE883} = {AA9952DB-2343-4C89-8273-FD34F1DC6D87} - {90EB2100-7BDB-4FFC-B657-3A63EEED93F9} = {AA9952DB-2343-4C89-8273-FD34F1DC6D87} - {154EB646-D902-41B5-9CE1-E78ACC63AA0A} = {AA9952DB-2343-4C89-8273-FD34F1DC6D87} + {B7B1E135-E27C-4189-9C12-95BC387EF130} = {2365F14C-C2A7-4BED-8EA1-EE290794E518} + {E396AC31-F60E-494A-BD4E-93A3C7E488AE} = {2365F14C-C2A7-4BED-8EA1-EE290794E518} + {154EB646-D902-41B5-9CE1-E78ACC63AA0A} = {2365F14C-C2A7-4BED-8EA1-EE290794E518} + {E6D34D70-393C-4B17-ACF9-8AF3B0B6E1A5} = {2365F14C-C2A7-4BED-8EA1-EE290794E518} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9697B8CE-91B1-4C5A-AB0E-94B684541F4D} + SolutionGuid = {0129167B-6AC5-4697-A3DC-C30DC5792351} EndGlobalSection EndGlobal diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index c6548542..d306df85 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -49,7 +49,7 @@ public abstract class BaseCommand /// /// Program name of the app. /// - protected const string ProgramApplicationAlias = "wingetcreate.exe"; + protected const string ProgramApplicationAlias = "wingetcreate"; /// /// Dictionary to store paths to downloaded installers from a given URL. @@ -388,15 +388,20 @@ protected static async Task DownloadPackageFile(string installerUrl) /// Bool indicating the validity of the manifest file. protected static bool ValidateManifest(string manifestPath) { - (bool success, string message) = WinGetUtil.ValidateManifest(manifestPath); + (bool result, string message) = WinGetUtil.ValidateManifest(manifestPath); - if (success) + if (result) { - Logger.InfoLocalized(nameof(Resources.ManifestValidationSucceeded_Message), success); + Logger.InfoLocalized(nameof(Resources.ManifestValidationSucceeded_Message), result); + } + else if (message == Constants.ManifestValidationUnavailable) + { + Logger.ErrorLocalized(nameof(Resources.ManifestValidationSucceeded_Message), message); + Logger.WarnLocalized(nameof(Resources.ManifestValidationUnavailableOnUnix_Message)); } else { - Logger.ErrorLocalized(nameof(Resources.ManifestValidationSucceeded_Message), success); + Logger.ErrorLocalized(nameof(Resources.ManifestValidationSucceeded_Message), result); Logger.Error(message); return false; } diff --git a/src/WingetCreateCLI/Commands/SettingsCommand.cs b/src/WingetCreateCLI/Commands/SettingsCommand.cs index 821de4aa..e59641ed 100644 --- a/src/WingetCreateCLI/Commands/SettingsCommand.cs +++ b/src/WingetCreateCLI/Commands/SettingsCommand.cs @@ -8,6 +8,7 @@ namespace Microsoft.WingetCreateCLI.Commands using System.ComponentModel; using System.Diagnostics; using System.IO; + using System.Runtime.InteropServices; using System.Threading.Tasks; using CommandLine; using Microsoft.WingetCreateCLI.Logging; @@ -50,7 +51,7 @@ public override Task Execute() } else { - this.DisplayParsingErrors(settingsFileErrors, UserSettings.SettingsJsonPath); + DisplayParsingErrors(settingsFileErrors, UserSettings.SettingsJsonPath); } } @@ -60,11 +61,11 @@ public override Task Execute() if (!isBackupValid) { - this.DisplayParsingErrors(backupFileErrors, UserSettings.SettingsBackupJsonPath); + DisplayParsingErrors(backupFileErrors, UserSettings.SettingsBackupJsonPath); } } - return Task.FromResult(commandEvent.IsSuccessful = this.OpenJsonFile(UserSettings.SettingsJsonPath)); + return Task.FromResult(commandEvent.IsSuccessful = OpenJsonFile(UserSettings.SettingsJsonPath)); } finally { @@ -72,7 +73,7 @@ public override Task Execute() } } - private void DisplayParsingErrors(List errors, string path) + private static void DisplayParsingErrors(List errors, string path) { Logger.WarnLocalized(nameof(Resources.ErrorParsingSettingsFile_Message), Path.GetFileName(path)); @@ -83,23 +84,39 @@ private void DisplayParsingErrors(List errors, string path) } } - private bool OpenJsonFile(string path) + private static bool OpenJsonFile(string path) { if (!File.Exists(path)) { return false; } - try + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = path }); - return true; + try + { + Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = path }); + } + catch (Win32Exception e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + return false; + } } - catch (Win32Exception e) + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); - return false; + Process.Start("xdg-open", path); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", path); + } + else + { + throw new PlatformNotSupportedException(); + } + + return true; } } } diff --git a/src/WingetCreateCLI/Common.cs b/src/WingetCreateCLI/Common.cs index e3c5b395..7d521bc2 100644 --- a/src/WingetCreateCLI/Common.cs +++ b/src/WingetCreateCLI/Common.cs @@ -5,7 +5,10 @@ namespace Microsoft.WingetCreateCLI { using System; using System.IO; + using System.Runtime.InteropServices; +#if WINDOWS using Windows.Storage; +#endif /// /// Helper class containing common functionality for the CLI. @@ -14,14 +17,19 @@ public static class Common { private const string ModuleName = "WindowsPackageManagerManifestCreator"; private const string UserProfileEnvironmentVariable = "%USERPROFILE%"; + private const string UserHomeDirectoryShortcutUnix = "~"; private const string LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%"; private const string TempEnvironmentVariable = "%TEMP%"; private static readonly Lazy AppStatePathLazy = new(() => { +#if WINDOWS string path = IsRunningAsUwp() ? ApplicationData.Current.LocalFolder.Path : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", ModuleName); +#else + string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "wingetcreate"); +#endif Directory.CreateDirectory(path); return path; }); @@ -81,26 +89,39 @@ public static string GetPathForDisplay(string path, bool substituteEnvironmentVa string localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); string tempPath = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar); - if (path.StartsWith(tempPath, StringComparison.OrdinalIgnoreCase)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return path.Replace(tempPath, TempEnvironmentVariable, StringComparison.OrdinalIgnoreCase); - } - else if (path.StartsWith(localAppDataPath, StringComparison.OrdinalIgnoreCase)) - { - return path.Replace(localAppDataPath, LocalAppDataEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + if (path.StartsWith(tempPath, StringComparison.OrdinalIgnoreCase)) + { + return path.Replace(tempPath, TempEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + } + else if (path.StartsWith(localAppDataPath, StringComparison.OrdinalIgnoreCase)) + { + return path.Replace(localAppDataPath, LocalAppDataEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + } + else if (path.StartsWith(userProfilePath, StringComparison.OrdinalIgnoreCase)) + { + return path.Replace(userProfilePath, UserProfileEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + } } - else if (path.StartsWith(userProfilePath, StringComparison.OrdinalIgnoreCase)) + else { - return path.Replace(userProfilePath, UserProfileEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + // Paths are case-sensitive on Unix + if (path.StartsWith(userProfilePath, StringComparison.Ordinal)) + { + return path.Replace(userProfilePath, UserHomeDirectoryShortcutUnix, StringComparison.Ordinal); + } } return path; } +#if WINDOWS private static bool IsRunningAsUwp() { DesktopBridge.Helpers helpers = new DesktopBridge.Helpers(); return helpers.IsRunningAsUwp(); } +#endif } } diff --git a/src/WingetCreateCLI/GitHubOAuth.cs b/src/WingetCreateCLI/GitHubOAuth.cs index 045807bd..f47fefd4 100644 --- a/src/WingetCreateCLI/GitHubOAuth.cs +++ b/src/WingetCreateCLI/GitHubOAuth.cs @@ -6,7 +6,9 @@ namespace Microsoft.WingetCreateCLI using System; using System.ComponentModel; using System.IO; +#if WINDOWS using System.Security.Cryptography; +#endif using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -50,7 +52,10 @@ public static string ReadTokenCache() if (File.Exists(TokenFile)) { var protectedBytes = File.ReadAllBytes(TokenFile); - var bytes = ProtectedData.Unprotect(protectedBytes, EntropyBytes, DataProtectionScope.CurrentUser); + var bytes = protectedBytes; +#if WINDOWS + bytes = ProtectedData.Unprotect(protectedBytes, EntropyBytes, DataProtectionScope.CurrentUser); +#endif return Encoding.UTF8.GetString(bytes); } else @@ -65,7 +70,10 @@ public static string ReadTokenCache() /// Token to be cached. public static void WriteTokenCache(string token) { - var bytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(token), EntropyBytes, DataProtectionScope.CurrentUser); + var bytes = Encoding.UTF8.GetBytes(token); +#if WINDOWS + bytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(token), EntropyBytes, DataProtectionScope.CurrentUser); +#endif File.WriteAllBytes(TokenFile, bytes); } diff --git a/src/WingetCreateCLI/Logger/Logger.cs b/src/WingetCreateCLI/Logger/Logger.cs index acc940de..4526800d 100644 --- a/src/WingetCreateCLI/Logger/Logger.cs +++ b/src/WingetCreateCLI/Logger/Logger.cs @@ -10,7 +10,6 @@ namespace Microsoft.WingetCreateCLI.Logging using Microsoft.WingetCreateCore.Common; using NLog; using NLog.Conditions; - using NLog.Config; using NLog.Targets; /// @@ -22,7 +21,7 @@ public static class Logger private static readonly FileTarget FileTarget = new() { - FileName = @$"{Path.Combine(Common.LocalAppStatePath, Constants.DiagnosticOutputDirectoryFolderName)}\WingetCreateLog-{DateTime.Now:yyyy-MM-dd-HH-mm.fff}.txt", + FileName = Path.Combine(Common.LocalAppStatePath, Constants.DiagnosticOutputDirectoryFolderName, $"WingetCreateLog-{DateTime.Now:yyyy-MM-dd-HH-mm.fff}.txt"), // Current layout example: 2021-01-01 08:30:59.0000|INFO|Microsoft.WingetCreateCLI.Commands.NewCommand.Execute|Log Message Example Layout = "${longdate}|${level:uppercase=true}|${callsite}|${message}", diff --git a/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml b/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml new file mode 100644 index 00000000..1a522901 --- /dev/null +++ b/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + arm64 + bin\ARM64\Release\net8.0-windows10.0.26100.0\win-arm64\publish\ + FileSystem + net8.0-windows10.0.26100.0 + win-arm64 + true + false + false + false + + \ No newline at end of file diff --git a/src/WingetCreateCLI/Properties/PublishProfiles/x64ReleasePublishProfile.pubxml b/src/WingetCreateCLI/Properties/PublishProfiles/x64ReleasePublishProfile.pubxml index b16e25e9..e52b0dad 100644 --- a/src/WingetCreateCLI/Properties/PublishProfiles/x64ReleasePublishProfile.pubxml +++ b/src/WingetCreateCLI/Properties/PublishProfiles/x64ReleasePublishProfile.pubxml @@ -1,18 +1,18 @@  Release x64 - bin\x64\Release\net8.0-windows10.0.22000.0\win-x64\publish\ + bin\x64\Release\net8.0-windows10.0.26100.0\win-x64\publish\ FileSystem - net8.0-windows10.0.22000.0 + net8.0-windows10.0.26100.0 win-x64 true - False - False - False + false + false + false \ No newline at end of file diff --git a/src/WingetCreateCLI/Properties/PublishProfiles/x86ReleasePublishProfile.pubxml b/src/WingetCreateCLI/Properties/PublishProfiles/x86ReleasePublishProfile.pubxml deleted file mode 100644 index a00c2386..00000000 --- a/src/WingetCreateCLI/Properties/PublishProfiles/x86ReleasePublishProfile.pubxml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Release - x86 - bin\x86\Release\net8.0-windows10.0.22000.0\win-x86\publish\ - FileSystem - net8.0-windows10.0.22000.0 - win-x86 - true - False - False - False - - \ No newline at end of file diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 5010840b..f28cba0d 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -1778,6 +1778,15 @@ public static string ManifestValidationSucceeded_Message { return ResourceManager.GetString("ManifestValidationSucceeded_Message", resourceCulture); } } + + /// + /// Looks up a localized string similar to Manifest validation is not available on Linux/macOS platforms. + /// + public static string ManifestValidationUnavailableOnUnix_Message { + get { + return ResourceManager.GetString("ManifestValidationUnavailableOnUnix_Message", resourceCulture); + } + } /// /// Looks up a localized string similar to The manifest syntax version. diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 65f945fc..0bb81bc5 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -437,13 +437,13 @@ Would you like to make changes to this manifest? - Execution of the command is paused until the GitHub login has been completed. + Execution of the command is paused until the GitHub login has been completed. Generating a preview of your manifests... - A GitHub account or personal access token must be linked in order to continue with this command. + A GitHub account or personal access token must be linked in order to continue with this command. Initiating GitHub login... @@ -582,6 +582,9 @@ Telemetry Settings + + Manifest validation is only available on Windows. + Unexpected error while loading settings. Please verify your settings by running the settings command. @@ -592,7 +595,7 @@ Invalid token provided, please generate a new GitHub token and try again. - Submitting a manifest without any updated changes is not allowed. + Submitting a manifest without any updated changes is not allowed. The following installers failed to match an existing installer node: diff --git a/src/WingetCreateCLI/WingetCreateCLI.csproj b/src/WingetCreateCLI/WingetCreateCLI.csproj index ada97f5d..d390c9d0 100644 --- a/src/WingetCreateCLI/WingetCreateCLI.csproj +++ b/src/WingetCreateCLI/WingetCreateCLI.csproj @@ -1,16 +1,26 @@ - + Exe - net8.0-windows10.0.22000.0 WingetCreateCLI Microsoft.WingetCreateCLI 1.9 - x64;x86 - win-x64;win-x86 + x64;ARM64 + win-x64;win-arm64;linux-x64;linux-arm64;osx-arm64 true TELEMETRYEVENTSOURCE_PUBLIC true + true + false + true + + + true + + net8.0-windows10.0.26100.0 + net8.0 @@ -21,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -56,14 +66,6 @@ - - - Resources.resx - True - True - - - @@ -75,24 +77,14 @@ Resources.Designer.cs PublicResXFileCodeGenerator + + $(IntermediateOutputPath)\Resources.Designer.cs + CSharp - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - + @@ -103,14 +95,6 @@ - - - - - - - - $(TargetFrameworkSDKToolsDirectory)$(PlatformTarget)\ - + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/Constants.cs b/src/WingetCreateCore/Common/Constants.cs index 5c898382..7f78e05a 100644 --- a/src/WingetCreateCore/Common/Constants.cs +++ b/src/WingetCreateCore/Common/Constants.cs @@ -67,5 +67,10 @@ public static class Constants /// The url path to the manifest documentation site. /// public const string ManifestDocumentationUrl = "https://aka.ms/winget-manifest-schema"; + + /// + /// Manifest validation unavailable on unix platforms. + /// + public const string ManifestValidationUnavailable = "skipped"; } } diff --git a/src/WingetCreateCore/Common/Msi/Msi.cs b/src/WingetCreateCore/Common/Msi/Msi.cs new file mode 100644 index 00000000..0f96e4f1 --- /dev/null +++ b/src/WingetCreateCore/Common/Msi/Msi.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +#pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter +#pragma warning disable SA1310 // Field names should not contain underscore +#pragma warning disable SA1600 // Elements should be documented +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Microsoft.WingetCreateCore.Common.Msi +{ + using System; + using System.Runtime.InteropServices; + using System.Text; + + public class Msi + { + public unsafe Msi(string path) + { + fixed (byte* pathPtr = Encoding.UTF8.GetBytes(path)) + { + MsiInformationFfi info = get_information(pathPtr); + this.Information = new MsiInformation + { + Architecture = GetString(info.arch), + Author = GetString(info.author), + Comments = GetString(info.comments), + CreatingApplication = GetString(info.creating_application), + CreationTime = GetString(info.creation_time), + Languages = GetStringArray(info.languages), + Subject = GetString(info.subject), + Title = GetString(info.title), + Uuid = GetString(info.uuid), + WordCount = info.word_count, + HasDigitalSignature = info.has_digital_signature, + TableNames = GetStringArray(info.table_names), + }; + free_information(info); + + this.Tables = new MsiTable[this.Information.TableNames.Length]; + for (int i = 0; i < this.Information.TableNames.Length; i++) + { + fixed (byte* tableNamePtr = Encoding.UTF8.GetBytes(this.Information.TableNames[i])) + { + String2DArrayFfi table_ffi = get_table(pathPtr, tableNamePtr); + string[][] table = GetString2DArray(table_ffi); + this.Tables[i] = new MsiTable + { + Name = this.Information.TableNames[i], + Columns = table[0], + Rows = table[1..], + }; + free_table(table_ffi); + } + } + } + } + + public MsiInformation Information { get; private set; } + + public MsiTable[] Tables { get; private set; } + + [DllImport("msi", ExactSpelling = true)] + private static unsafe extern MsiInformationFfi get_information(byte* path); + + [DllImport("msi", ExactSpelling = true)] + private static unsafe extern String2DArrayFfi get_table(byte* path, byte* table_name); + + [DllImport("msi", ExactSpelling = true)] + private static unsafe extern void free_information(MsiInformationFfi info); + + [DllImport("msi", ExactSpelling = true)] + private static unsafe extern void free_table(String2DArrayFfi table); + + private static unsafe string GetString(StringFfi s) + { + try + { + byte[] byteArray = new byte[s.len.ToUInt32()]; + Marshal.Copy((IntPtr)s.ptr, byteArray, 0, (int)s.len); + return Encoding.UTF8.GetString(byteArray); + } + catch + { + return string.Empty; + } + } + + private static unsafe string[] GetStringArray(StringArrayFfi x) + { + string[] result = new string[x.len.ToUInt32()]; + for (int i = 0; i < result.Length; i++) + { + int offset = i * Marshal.SizeOf(); + result[i] = GetString(Marshal.PtrToStructure((IntPtr)x.ptr + offset)); + } + + return result; + } + + private static unsafe string[][] GetString2DArray(String2DArrayFfi x) + { + string[][] result = new string[x.len.ToUInt32()][]; + for (int i = 0; i < result.Length; i++) + { + int offset = i * Marshal.SizeOf(); + result[i] = GetStringArray(Marshal.PtrToStructure((IntPtr)x.ptr + offset)); + } + + return result; + } + + public struct MsiInformation + { + public string Architecture; + public string Author; + public string Comments; + public string CreatingApplication; + public string CreationTime; + public string[] Languages; + public string Subject; + public string Title; + public string Uuid; + public int WordCount; + public bool HasDigitalSignature; + public string[] TableNames; + } + + public struct MsiTable + { + public string Name; + public string[] Columns; + public string[][] Rows; + } + + [StructLayout(LayoutKind.Sequential, Size = 24)] + private unsafe struct StringFfi + { + public byte* ptr; + public UIntPtr len; + public UIntPtr cap; + } + + [StructLayout(LayoutKind.Sequential, Size = 24)] + private unsafe struct StringArrayFfi + { + public StringFfi* ptr; + public UIntPtr len; + public UIntPtr cap; + } + + [StructLayout(LayoutKind.Sequential, Size = 24)] + private unsafe struct String2DArrayFfi + { + public StringArrayFfi* ptr; + public UIntPtr len; + public UIntPtr cap; + } + + [StructLayout(LayoutKind.Sequential, Size = 248)] + private struct MsiInformationFfi + { + public StringFfi arch; + public StringFfi author; + public StringFfi comments; + public StringFfi creating_application; + public StringFfi creation_time; + public StringArrayFfi languages; + public StringFfi subject; + public StringFfi title; + public StringFfi uuid; + public int word_count; + + [MarshalAs(UnmanagedType.U1)] + public bool has_digital_signature; + + public StringArrayFfi table_names; + } + } +} diff --git a/src/WingetCreateCore/Common/Msi/rust-msi b/src/WingetCreateCore/Common/Msi/rust-msi new file mode 160000 index 00000000..0dde3669 --- /dev/null +++ b/src/WingetCreateCore/Common/Msi/rust-msi @@ -0,0 +1 @@ +Subproject commit 0dde36697a1a8c3406dd775ddeae77fc5a0dca10 diff --git a/src/WingetCreateCore/Common/Msix.cs b/src/WingetCreateCore/Common/Msix.cs new file mode 100644 index 00000000..acf99771 --- /dev/null +++ b/src/WingetCreateCore/Common/Msix.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +#pragma warning disable SA1649 // File name should match first type name + +namespace Microsoft.WingetCreateCore.Common +{ + using System; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using Microsoft.WingetCreateCore.Models.Installer; + + /// + /// Class to handle MSIX and APPX packages and extract metadata from them. + /// + public class MsixOrAppxPackage + { + /// + /// Appx Manifest file name. + /// + public const string ManifestFile = "AppxManifest.xml"; + + /// + /// Appx Bundle Manifest file name. + /// + public const string BundleManifestFile = "AppxMetadata/AppxBundleManifest.xml"; + + /// + /// Appx Signature file name. + /// + public const string SignatureFile = "AppxSignature.p7x"; + + /// + /// Initializes a new instance of the class. + /// + /// Path to the MSIX/APPX package or bundle. + public MsixOrAppxPackage(string path) + { + using ZipArchive zipArchive = new(File.OpenRead(path), ZipArchiveMode.Read); + + using MemoryStream appxSignatureStream = new MemoryStream(); + zipArchive.GetEntry(SignatureFile).Open().CopyTo(appxSignatureStream); + + string appxManifestXml = new StreamReader(zipArchive.GetEntry(ManifestFile).Open()).ReadToEnd(); + var appxManifest = new System.Xml.XmlDocument(); + appxManifest.LoadXml(appxManifestXml); + + // /Package/Identity + var identityNode = appxManifest.SelectSingleNode("/*[local-name()='Package']/*[local-name()='Identity']"); + + // /Package/Properties + var propertiesNode = appxManifest.SelectSingleNode("/*[local-name()='Package']/*[local-name()='Properties']"); + + // /Package/Dependencies/TargetDeviceFamily + var targetDeviceFamilyNode = appxManifest.SelectSingleNode("/*[local-name()='Package']/*[local-name()='Dependencies']/*[local-name()='TargetDeviceFamily']"); + + // Generate the hash part of the package family name + // Source: https://marcinotorowski.com/2021/12/19/calculating-hash-part-of-msix-package-family-name + var publisherSha256 = SHA256.HashData(Encoding.Unicode.GetBytes(identityNode.Attributes["Publisher"].Value)); + var binaryString = string.Concat(publisherSha256.Take(8).Select(c => Convert.ToString(c, 2).PadLeft(8, '0'))) + '0'; // representing 65-bits = 13 * 5 + var encodedPublisherId = string.Concat(Enumerable.Range(0, binaryString.Length / 5).Select(i => "0123456789ABCDEFGHJKMNPQRSTVWXYZ".Substring(Convert.ToInt32(binaryString.Substring(i * 5, 5), 2), 1))); + + this.Information = new Metadata + { + SignatureSha256 = GetSignatureSha256(appxSignatureStream), + PackageFamilyName = $"{identityNode.Attributes["Name"]?.Value}_{encodedPublisherId.ToLower()}", + PackageVersion = identityNode.Attributes["Version"]?.Value, + Architecture = identityNode.Attributes["ProcessorArchitecture"]?.Value, + PackageName = propertiesNode.SelectSingleNode("*[local-name()='DisplayName']")?.InnerText, + Publisher = propertiesNode.SelectSingleNode("*[local-name()='PublisherDisplayName']")?.InnerText, + ShortDescription = propertiesNode.SelectSingleNode("*[local-name()='Description']")?.InnerText, + MinimumOSVersion = targetDeviceFamilyNode.Attributes["MinVersion"]?.Value, + Platforms = [Enum.Parse(targetDeviceFamilyNode.Attributes["Name"]?.Value.Replace('.', '_'))], + }; + } + + /// + /// Gets or sets metadata of APPX/MSIX package or bundle. + /// + public Metadata Information { get; set; } + + /// + /// Gets signature sha256 of APPX/MSIX package or bundle. + /// + /// Stream of the AppxSignature.p7x file. + /// Signature sha256 of the package. + public static string GetSignatureSha256(MemoryStream appxSignatureP7x_stream) + { + return BitConverter.ToString(SHA256.HashData(appxSignatureP7x_stream.ToArray())).Replace("-", string.Empty); + } + + /// + /// Structure to hold the metadata of the package. + /// + public struct Metadata + { + /// + /// Gets signature of MSIX/APPX package or bundle. + /// + public string SignatureSha256; + + /// + /// Gets the package family name. + /// + public string PackageFamilyName; + + /// + /// Gets the package version (Identity#Version). + /// + public string PackageVersion; + + /// + /// Gets the package architecture (Identity#ProcessorArchitecture). + /// + public string Architecture; + + /// + /// Gets the package name (Properties/DisplayName). + /// + public string PackageName; + + /// + /// Gets the publisher name (Properties/PublisherDisplayName). + /// + public string Publisher; + + /// + /// Gets the description of the package (Properties/Description). + /// + public string ShortDescription; + + /// + /// Gets the minimum OS version required to run the package (Dependencies/TargetDeviceFamily#MinVersion). + /// + public string MinimumOSVersion; + + /// + /// Gets the platform(s) supported by the package (Dependencies/TargetDeviceFamily#Name). + /// + public Platform[] Platforms; + } + } +} diff --git a/src/WingetCreateCore/Common/PackageParser.cs b/src/WingetCreateCore/Common/PackageParser.cs index 447fefc6..ee22e805 100644 --- a/src/WingetCreateCore/Common/PackageParser.cs +++ b/src/WingetCreateCore/Common/PackageParser.cs @@ -5,28 +5,29 @@ namespace Microsoft.WingetCreateCore { using System; using System.Collections.Generic; - using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; + using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Security.Cryptography; + using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; - using Microsoft.Deployment.WindowsInstaller.Linq; - using Microsoft.Msix.Utils; - using Microsoft.Msix.Utils.AppxPackaging; - using Microsoft.Msix.Utils.AppxPackagingInterop; + using System.Xml.Linq; + using AsmResolver.PE; + using AsmResolver.PE.Win32Resources; + using AsmResolver.PE.Win32Resources.Version; using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Common.Exceptions; + using Microsoft.WingetCreateCore.Common.Msi; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; using Microsoft.WingetCreateCore.Models.Version; using Newtonsoft.Json; - using Vestris.ResourceLib; /// /// Provides functionality for a parsing and extracting relevant metadata from a given package. @@ -529,13 +530,22 @@ private static bool ParsePackage(InstallerMetadata installerMetadata, Manifests InstallerManifest installerManifest = manifests.InstallerManifest; DefaultLocaleManifest defaultLocaleManifest = manifests.DefaultLocaleManifest; - var versionInfo = FileVersionInfo.GetVersionInfo(installerMetadata.PackageFile); - - defaultLocaleManifest.PackageVersion ??= versionInfo.FileVersion?.Trim() ?? versionInfo.ProductVersion?.Trim(); - defaultLocaleManifest.Publisher ??= versionInfo.CompanyName?.Trim(); - defaultLocaleManifest.PackageName ??= versionInfo.ProductName?.Trim(); - defaultLocaleManifest.ShortDescription ??= versionInfo.FileDescription?.Trim(); - defaultLocaleManifest.Copyright ??= versionInfo.LegalCopyright?.Trim(); + try + { + var versionInfoResource = VersionInfoResource.FromDirectory(PEImage.FromFile(installerMetadata.PackageFile).Resources); + var versionInfo = versionInfoResource.GetChild("StringFileInfo").Tables.First(); + + defaultLocaleManifest.PackageVersion ??= versionInfo["FileVersion"]?.Trim() ?? versionInfo["ProductVersion"]?.Trim(); + defaultLocaleManifest.Publisher ??= versionInfo["CompanyName"]?.Trim(); + defaultLocaleManifest.PackageName ??= versionInfo["ProductName"]?.Trim(); + defaultLocaleManifest.ShortDescription ??= versionInfo["FileDescription"]?.Trim(); + defaultLocaleManifest.Description ??= versionInfo["Comments"]?.Trim(); + defaultLocaleManifest.Copyright ??= versionInfo["LegalCopyright"]?.Trim(); + } + catch (Exception ex) when (ex is BadImageFormatException || ex is KeyNotFoundException) + { + // do nothing + } if (ParsePackageAndGenerateInstallerNodes(installerMetadata, manifests)) { @@ -693,23 +703,6 @@ private static bool ParsePackageAndGenerateInstallerNodes(InstallerMetadata inst return archMatches.Count == 1 ? archMatches.Single() : null; } - /// - /// Computes the SHA256 hash value for the specified byte array. - /// - /// The input to compute the hash code for. - /// The computed SHA256 hash string. - private static string HashBytes(byte[] buffer) - { - using var hasher = SHA256.Create(); - return BitConverter.ToString(hasher.ComputeHash(buffer)).Remove("-"); - } - - private static string HashAppxFile(IAppxFile signatureFile) - { - var signatureBytes = StreamUtils.ReadStreamToByteArray(signatureFile.GetStream()); - return HashBytes(signatureBytes); - } - private static MachineType? GetMachineType(string binary) { using (FileStream stream = File.OpenRead(binary)) @@ -814,51 +807,38 @@ private static bool ParseExeInstallerType(string path, Installer baseInstaller, { try { - ManifestResource rc = new ManifestResource(); InstallerType? installerTypeEnum; - try + var directory = PEImage.FromFile(path).Resources + .GetDirectory(ResourceType.Manifest) + .GetDirectory(ResourceType.Cursor); + var data = directory.GetData(directory.Entries.First().Id); + var manifestXml = new XmlDocument(); + + // The first character is not a valid XML character, so we skip it. + manifestXml.LoadXml(data.CreateReader().ReadUtf8String().ToString()[1..]); + string installerType = manifestXml.DocumentElement + .GetElementsByTagName("description") + .Cast() + .FirstOrDefault()? + .InnerText? + .Split(' ').First() + .ToLowerInvariant(); + + if (installerType.EqualsIC("wix")) { - rc.LoadFrom(path); - string installerType = rc.Manifest.DocumentElement - .GetElementsByTagName("description") - .Cast() - .FirstOrDefault()? - .InnerText? - .Split(' ').First() - .ToLowerInvariant(); - - if (installerType.EqualsIC("wix")) - { - // See https://github.com/microsoft/winget-create/issues/26, a Burn installer is an exe-installer produced by the WiX toolset. - installerTypeEnum = InstallerType.Burn; - } - else if (KnownInstallerResourceNames.Contains(installerType)) - { - // If it's a known exe installer type, set as appropriately - installerTypeEnum = installerType.ToEnumOrDefault(); - } - else - { - installerTypeEnum = (baseInstaller.InstallerType == InstallerType.Portable || - baseInstaller.NestedInstallerType == NestedInstallerType.Portable) ? - InstallerType.Portable : InstallerType.Exe; - } + // See https://github.com/microsoft/winget-create/issues/26, a Burn installer is an exe-installer produced by the WiX toolset. + installerTypeEnum = InstallerType.Burn; } - catch (Win32Exception err) + else if (KnownInstallerResourceNames.Contains(installerType)) { - if ((err.Message == "The specified resource type cannot be found in the image file." - && err.NativeErrorCode == 1813) || - (err.Message == "The specified image file did not contain a resource section." - && err.NativeErrorCode == 1812)) - { - installerTypeEnum = (baseInstaller.InstallerType == InstallerType.Portable || - baseInstaller.NestedInstallerType == NestedInstallerType.Portable) ? - InstallerType.Portable : InstallerType.Exe; - } - else - { - return false; - } + // If it's a known exe installer type, set as appropriately + installerTypeEnum = installerType.ToEnumOrDefault(); + } + else + { + installerTypeEnum = (baseInstaller.InstallerType == InstallerType.Portable || + baseInstaller.NestedInstallerType == NestedInstallerType.Portable) ? + InstallerType.Portable : InstallerType.Exe; } SetInstallerType(baseInstaller, installerTypeEnum.Value); @@ -869,97 +849,84 @@ private static bool ParseExeInstallerType(string path, Installer baseInstaller, return true; } - catch (Win32Exception) + catch (Exception ex) when (ex is BadImageFormatException || ex is KeyNotFoundException) { return false; } } - /// - /// Checks if a MSI Installer database was generated by WiX, based on common characteristics. - /// - /// A MSI Installer database. - /// A boolean. - private static bool IsWix(QDatabase installer) - { - return - installer.Tables.AsEnumerable().Any(table => table.Name.ToLower().Contains("wix")) || - installer.Properties.AsEnumerable().Any(property => property.Property.ToLower().Contains("wix") || property.Value.ToLower().Contains("wix")) || - installer.SummaryInfo.CreatingApp.ToLower().Contains("wix") || - installer.SummaryInfo.CreatingApp.ToLower().Contains("windows installer xml"); - } - private static bool ParseMsi(string path, Installer baseInstaller, Manifests manifests, List newInstallers) { DefaultLocaleManifest defaultLocaleManifest = manifests?.DefaultLocaleManifest; - try + Msi msiInfo = new Msi(path); + + // Subject is a mandatory property in an MSI, equals to ProductName in the Property table + // https://learn.microsoft.com/en-us/windows/win32/msi/summary-property-descriptions + if (msiInfo.Information.Subject == null) { - using (var database = new QDatabase(path, Deployment.WindowsInstaller.DatabaseOpenMode.ReadOnly)) - { - InstallerType installerType = IsWix(database) - ? InstallerType.Wix - : InstallerType.Msi; - SetInstallerType(baseInstaller, installerType); + // Binary isn't a valid MSI or wasn't an MSI, skip + return false; + } - var properties = database.Properties.ToList(); + Msi.MsiTable propertyTable = msiInfo.Tables.Where(table => table.Name == "Property").First(); - if (defaultLocaleManifest != null) - { - defaultLocaleManifest.PackageVersion ??= properties.FirstOrDefault(p => p.Property == "ProductVersion")?.Value; - defaultLocaleManifest.PackageName ??= properties.FirstOrDefault(p => p.Property == "ProductName")?.Value; - defaultLocaleManifest.Publisher ??= properties.FirstOrDefault(p => p.Property == "Manufacturer")?.Value; - } + bool isWix = msiInfo.Information.TableNames.Any(table_name => table_name.ToLower().Contains("wix")) || + propertyTable.Rows.Any(row => row[0].ToLower().Contains("wix") || row[1].ToLower().Contains("wix")) || + msiInfo.Information.CreatingApplication.ToLower().Contains("wix") || + msiInfo.Information.CreatingApplication.ToLower().Contains("windows installer xml"); + + SetInstallerType(baseInstaller, isWix ? InstallerType.Wix : InstallerType.Msi); + + if (defaultLocaleManifest != null) + { + defaultLocaleManifest.PackageVersion ??= propertyTable.Rows.Where(row => row[0] == "\"ProductVersion\"").First()[1].Trim('"'); + defaultLocaleManifest.PackageName ??= propertyTable.Rows.Where(row => row[0] == "\"ProductName\"").First()[1].Trim('"'); + defaultLocaleManifest.Publisher ??= propertyTable.Rows.Where(row => row[0] == "\"Manufacturer\"").First()[1].Trim('"'); + } - baseInstaller.ProductCode = properties.FirstOrDefault(p => p.Property == "ProductCode")?.Value; - baseInstaller.AppsAndFeaturesEntries = new List + baseInstaller.ProductCode = propertyTable.Rows.Where(row => row[0] == "\"ProductCode\"").First()[1].Trim('"'); + baseInstaller.AppsAndFeaturesEntries = new List + { + new AppsAndFeaturesEntry { - new AppsAndFeaturesEntry - { - DisplayName = properties.FirstOrDefault(p => p.Property == "ProductName")?.Value, - DisplayVersion = properties.FirstOrDefault(p => p.Property == "ProductVersion")?.Value, - Publisher = properties.FirstOrDefault(p => p.Property == "Manufacturer")?.Value, - ProductCode = properties.FirstOrDefault(p => p.Property == "ProductCode")?.Value, - UpgradeCode = properties.FirstOrDefault(p => p.Property == "UpgradeCode")?.Value, - }, - }; + DisplayName = propertyTable.Rows.Where(row => row[0] == "\"ProductName\"").First()[1].Trim('"'), + DisplayVersion = propertyTable.Rows.Where(row => row[0] == "\"ProductVersion\"").First()[1].Trim('"'), + Publisher = propertyTable.Rows.Where(row => row[0] == "\"Manufacturer\"").First()[1].Trim('"'), + ProductCode = propertyTable.Rows.Where(row => row[0] == "\"ProductCode\"").First()[1].Trim('"'), + UpgradeCode = propertyTable.Rows.Where(row => row[0] == "\"UpgradeCode\"").First()[1].Trim('"'), + }, + }; - string archString = database.SummaryInfo.Template.Split(';').First(); + string archString = msiInfo.Information.Architecture; - archString = archString.EqualsIC("Intel") ? "x86" : - archString.EqualsIC("Intel64") ? "x64" : - archString.EqualsIC("Arm64") ? "Arm64" : - archString.EqualsIC("Arm") ? "Arm" : archString; + archString = archString.EqualsIC("Intel") ? "x86" : + archString.EqualsIC("Intel64") ? "x64" : + archString.EqualsIC("Arm64") ? "Arm64" : + archString.EqualsIC("Arm") ? "Arm" : archString; - baseInstaller.Architecture = archString.ToEnumOrDefault() ?? Architecture.Neutral; + baseInstaller.Architecture = archString.ToEnumOrDefault() ?? Architecture.Neutral; - if (baseInstaller.InstallerLocale == null) - { - string languageString = properties.FirstOrDefault(p => p.Property == "ProductLanguage")?.Value; + if (baseInstaller.InstallerLocale == null) + { + string languageString = propertyTable.Rows.Where(row => row[0] == "\"ProductLanguage\"").First()[1].Trim('"'); - if (int.TryParse(languageString, out int lcid)) - { - try - { - baseInstaller.InstallerLocale = new CultureInfo(lcid).Name; - } - catch (Exception ex) when (ex is ArgumentOutOfRangeException || ex is CultureNotFoundException) - { - // If the lcid value is invalid, do nothing. - } - } + if (int.TryParse(languageString, out int lcid)) + { + try + { + baseInstaller.InstallerLocale = new CultureInfo(lcid).Name; + } + catch (Exception ex) when (ex is ArgumentOutOfRangeException || ex is CultureNotFoundException) + { + // If the lcid value is invalid, do nothing. } } + } - newInstallers?.Add(baseInstaller); + newInstallers?.Add(baseInstaller); - return true; - } - catch (Deployment.WindowsInstaller.InstallerException) - { - // Binary wasn't an MSI, skip - return false; - } + return true; } private static bool ParseMsix(string path, Installer baseInstaller, Manifests manifests, List newInstallers) @@ -967,8 +934,8 @@ private static bool ParseMsix(string path, Installer baseInstaller, Manifests ma InstallerManifest installerManifest = manifests?.InstallerManifest; DefaultLocaleManifest defaultLocaleManifest = manifests?.DefaultLocaleManifest; - AppxMetadata metadata = GetAppxMetadataAndSetInstallerProperties(path, installerManifest, baseInstaller, newInstallers); - if (metadata == null) + MsixOrAppxPackage.Metadata metadata = GetAppxMetadataAndSetInstallerProperties(path, installerManifest, baseInstaller, newInstallers); + if (metadata.Equals(default(MsixOrAppxPackage.Metadata))) { // Binary wasn't an MSIX, skip return false; @@ -976,55 +943,15 @@ private static bool ParseMsix(string path, Installer baseInstaller, Manifests ma if (defaultLocaleManifest != null) { - defaultLocaleManifest.PackageVersion ??= metadata.Version?.ToString(); - defaultLocaleManifest.PackageName ??= metadata.DisplayName; - defaultLocaleManifest.Publisher ??= metadata.PublisherDisplayName; - defaultLocaleManifest.ShortDescription ??= GetApplicationProperty(metadata, "Description"); + defaultLocaleManifest.PackageVersion ??= metadata.PackageVersion; + defaultLocaleManifest.PackageName ??= metadata.PackageName; + defaultLocaleManifest.Publisher ??= metadata.Publisher; + defaultLocaleManifest.ShortDescription ??= metadata.ShortDescription; } return true; } - private static string GetApplicationProperty(AppxMetadata appxMetadata, string propertyName) - { - IAppxManifestApplicationsEnumerator enumerator = appxMetadata.AppxReader.GetManifest().GetApplications(); - - while (enumerator.GetHasCurrent()) - { - IAppxManifestApplication application = enumerator.GetCurrent(); - - try - { - application.GetStringValue(propertyName, out string value); - return value; - } - catch (ArgumentException) - { - // Property not found on this node, continue - } - - enumerator.MoveNext(); - } - - return null; - } - - private static void SetInstallerPropertiesFromAppxMetadata(AppxMetadata appxMetadata, Installer installer, InstallerManifest installerManifest) - { - installer.Architecture = appxMetadata.Architecture.ToEnumOrDefault() ?? Architecture.Neutral; - - installer.MinimumOSVersion = SetInstallerStringPropertyIfNeeded(installerManifest?.MinimumOSVersion, appxMetadata.MinOSVersion?.ToString()); - installer.PackageFamilyName = SetInstallerStringPropertyIfNeeded(installerManifest?.PackageFamilyName, appxMetadata.PackageFamilyName); - - // We have to fixup the Platform string first, and then remove anything that fails to parse. - var platformValues = appxMetadata.TargetDeviceFamiliesMinVersions.Keys - .Select(k => k.Replace('.', '_').ToEnumOrDefault()) - .Where(p => p != null) - .Select(p => p.Value) - .ToList(); - installer.Platform = SetInstallerListPropertyIfNeeded(installerManifest?.Platform, platformValues); - } - private static string SetInstallerStringPropertyIfNeeded(string rootProperty, string valueToSet) { return valueToSet == rootProperty ? null : valueToSet; @@ -1035,41 +962,61 @@ private static List SetInstallerListPropertyIfNeeded(List rootProperty, return rootProperty != null && new HashSet(rootProperty).SetEquals(valueToSet) ? null : valueToSet; } - private static AppxMetadata GetAppxMetadataAndSetInstallerProperties(string path, InstallerManifest installerManifest, Installer baseInstaller, List installers) + private static MsixOrAppxPackage.Metadata GetAppxMetadataAndSetInstallerProperties(string path, InstallerManifest installerManifest, Installer baseInstaller, List installers) { + // Create a folder with the same name as the downloaded installer. + // This will be used to extract individual MSIX/APPX packages from an MSIX/APPX bundle. + string installerFileWithoutExtension = Path.GetFileNameWithoutExtension(path); + if (!Directory.Exists(installerFileWithoutExtension)) + { + Directory.CreateDirectory(installerFileWithoutExtension); + } + try { - var appxMetadatas = new List(); + var appxMetadatas = new List(); string signatureSha256; try { + ZipArchive zipArchive = new(File.OpenRead(path), ZipArchiveMode.Read); + // Check if package is an MsixBundle - var bundle = new AppxBundleMetadata(path); + string appxBundleManifestXml = new StreamReader(zipArchive.GetEntry(MsixOrAppxPackage.BundleManifestFile).Open()).ReadToEnd(); + var appxBundleManifest = new XmlDocument(); + appxBundleManifest.LoadXml(appxBundleManifestXml); + + using MemoryStream appxSignatureStream = new MemoryStream(); + zipArchive.GetEntry(MsixOrAppxPackage.SignatureFile).Open().CopyTo(appxSignatureStream); + signatureSha256 = MsixOrAppxPackage.GetSignatureSha256(appxSignatureStream); - IAppxFile signatureFile = bundle.AppxBundleReader.GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE.APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE); - signatureSha256 = HashAppxFile(signatureFile); + // /Bundle/Packages/Package[@Type='application'] -> FileName + var packages = appxBundleManifest + .SelectNodes("/*[local-name()='Bundle']/*[local-name()='Packages']/*[local-name()='Package']") + .Cast() + .Where(p => p.Attributes["Type"].Value == "application") + .Select(p => p.Attributes["FileName"].Value); // Only create installer nodes for non-resource packages - foreach (var childPackage in bundle.ChildAppxPackages.Where(p => p.PackageType == PackageType.Application)) + foreach (var childPackage in packages) { // Ignore stub packages. - if (childPackage.RelativeFilePath.StartsWith("AppxMetadata\\Stub", StringComparison.OrdinalIgnoreCase)) + if (childPackage.StartsWith("AppxMetadata\\Stub", StringComparison.OrdinalIgnoreCase)) { continue; } - var appxFile = bundle.AppxBundleReader.GetPayloadPackage(childPackage.RelativeFilePath); - appxMetadatas.Add(new AppxMetadata(appxFile.GetStream())); + string extractPath = Path.Combine(installerFileWithoutExtension, childPackage); + zipArchive.GetEntry(childPackage).ExtractToFile(extractPath, true); + appxMetadatas.Add(new MsixOrAppxPackage(extractPath).Information); } } - catch (System.Runtime.InteropServices.COMException) + catch { // Check if package is an Msix - var appxMetadata = new AppxMetadata(path); + MsixOrAppxPackage.Metadata appxMetadata = new MsixOrAppxPackage(path).Information; appxMetadatas.Add(appxMetadata); - IAppxFile signatureFile = appxMetadata.AppxReader.GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE.APPX_FOOTPRINT_FILE_TYPE_SIGNATURE); - signatureSha256 = HashAppxFile(signatureFile); + signatureSha256 = appxMetadata.SignatureSha256; } baseInstaller.SignatureSha256 = signatureSha256; @@ -1079,17 +1026,32 @@ private static AppxMetadata GetAppxMetadataAndSetInstallerProperties(string path foreach (var appxMetadata in appxMetadatas) { var msixInstaller = CloneInstaller(baseInstaller); - installers.Add(msixInstaller); - SetInstallerPropertiesFromAppxMetadata(appxMetadata, msixInstaller, installerManifest); + msixInstaller.Architecture = appxMetadata.Architecture.ToEnumOrDefault() ?? Architecture.Neutral; + + msixInstaller.MinimumOSVersion = SetInstallerStringPropertyIfNeeded(installerManifest?.MinimumOSVersion, appxMetadata.MinimumOSVersion); + msixInstaller.PackageFamilyName = SetInstallerStringPropertyIfNeeded(installerManifest?.PackageFamilyName, appxMetadata.PackageFamilyName); + msixInstaller.Platform = SetInstallerListPropertyIfNeeded(installerManifest?.Platform, [.. appxMetadata.Platforms]); + + installers.Add(msixInstaller); } return appxMetadatas.First(); } - catch (System.Runtime.InteropServices.COMException) + catch { // Binary wasn't an MSIX - return null; + return default; + } + finally + { + // Delete the folder created to extract individual MSIX/APPX packages. + // This is done to prevent additional disk space being consumed by + // individual MSIX/APPX packages extracted from an MSIX/APPX bundle. + if (Directory.Exists(installerFileWithoutExtension)) + { + Directory.Delete(installerFileWithoutExtension, true); + } } } diff --git a/src/WingetCreateCore/Common/Utils.cs b/src/WingetCreateCore/Common/Utils.cs index 365e1cf5..fdc0920d 100644 --- a/src/WingetCreateCore/Common/Utils.cs +++ b/src/WingetCreateCore/Common/Utils.cs @@ -38,10 +38,11 @@ public static string GetEntryAssemblyName() /// Package Version. /// Delimiter character of the generated path. /// Full directory path where the manifests should be saved to. - public static string GetAppManifestDirPath(string packageId, string version, char pathDelimiter = '\\') + public static string GetAppManifestDirPath(string packageId, string version, char pathDelimiter = '?') { + pathDelimiter = pathDelimiter == '?' ? Path.DirectorySeparatorChar : pathDelimiter; string path = Path.Combine(Constants.WingetManifestRoot, $"{char.ToLowerInvariant(packageId[0])}", packageId.Replace('.', '\\'), version); - return pathDelimiter != '\\' ? path.Replace('\\', pathDelimiter) : path; + return path.Replace('\\', pathDelimiter); } /// diff --git a/src/WingetCreateCore/Common/WinGetUtil.cs b/src/WingetCreateCore/Common/WinGetUtil.cs index 8f2e658c..ef2bbf69 100644 --- a/src/WingetCreateCore/Common/WinGetUtil.cs +++ b/src/WingetCreateCore/Common/WinGetUtil.cs @@ -1,17 +1,23 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. +#pragma warning disable CS0162 // Unreachable code detected + namespace Microsoft.WingetCreateCore.Common { using System; +#if WINDOWS using System.Runtime.InteropServices; +#endif /// /// Wrapper class for utilizing WinGetUtil.dll functionality. /// public static class WinGetUtil { +#pragma warning disable IDE0051 // Remove unused private members private const string DllName = @"WinGetUtil.dll"; +#pragma warning restore IDE0051 // Remove unused private members /// /// Validates the manifest is compliant. @@ -20,12 +26,16 @@ public static class WinGetUtil /// Message from manifest validation. public static (bool Succeeded, string FailureOrWarningMessage) ValidateManifest(string manifestPath) { +#if WINDOWS WinGetValidateManifest( manifestPath, out bool succeeded, out string failureOrWarningMessage); return (succeeded, failureOrWarningMessage); +#endif + + return (false, Constants.ManifestValidationUnavailable); } /// @@ -36,11 +46,20 @@ public static (bool Succeeded, string FailureOrWarningMessage) ValidateManifest( /// Int representing the version comparison result. public static int CompareVersions(string versionA, string versionB) { +#if WINDOWS int hr = WinGetCompareVersions(versionA, versionB, out int comparisonResult); Marshal.ThrowExceptionForHR(hr); return comparisonResult; +#endif + + // Since WinGetUtil.dll is not available on non-Windows platforms, we will do a simple version comparison. + // First, try to parse the versions SemVer. If it fails, we will use string comparison. + return Version.TryParse(versionA, out Version versionAObj) && Version.TryParse(versionB, out Version versionBObj) + ? versionAObj.CompareTo(versionBObj) + : string.Compare(versionA, versionB, StringComparison.OrdinalIgnoreCase); } +#if WINDOWS /// /// Validates a given manifest. Returns a bool for validation result and /// a string representing validation errors if validation failed. @@ -60,5 +79,6 @@ private static extern int WinGetCompareVersions( string versionA, string versionB, out int comparisonResult); +#endif } } diff --git a/src/WingetCreateCore/Serializers/YamlSerializer.cs b/src/WingetCreateCore/Serializers/YamlSerializer.cs index 79de760e..a952104d 100644 --- a/src/WingetCreateCore/Serializers/YamlSerializer.cs +++ b/src/WingetCreateCore/Serializers/YamlSerializer.cs @@ -136,6 +136,10 @@ public AliasTypeInspector(ITypeInspector innerTypeDescriptor) this.innerTypeDescriptor = innerTypeDescriptor; } + public override string GetEnumName(Type enumType, string name) => this.innerTypeDescriptor.GetEnumName(enumType, name); + + public override string GetEnumValue(object enumValue) => this.innerTypeDescriptor.GetEnumValue(enumValue); + /// /// Because certain properties were generated incorrectly, we needed to create custom fields for those properties. /// Therefore to resolve naming conflicts during deserialization, we prioritize fields that have the YamlMemberAttribute defined @@ -182,7 +186,7 @@ public bool Accepts(Type type) return type.IsEnum || ((u != null) && u.IsEnum); } - public object ReadYaml(IParser parser, Type type) + public object ReadYaml(IParser parser, Type type, ObjectDeserializer objectDeserializer) { Type u = Nullable.GetUnderlyingType(type); if (u != null) @@ -202,7 +206,7 @@ public object ReadYaml(IParser parser, Type type) return Enum.Parse(type, serializableValues[parsedEnum.Value].Name); } - public void WriteYaml(IEmitter emitter, object value, Type type) + public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer objectSerializer) { var enumMember = type.GetMember(value.ToString()).FirstOrDefault(); var yamlValue = enumMember?.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault() ?? value.ToString(); @@ -217,14 +221,14 @@ public YamlSkipPropertyVisitor(IObjectGraphVisitor nextVisitor) { } - public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context) + public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer objectSerializer) { if (key.Name == "AdditionalProperties") { return false; } - return base.EnterMapping(key, value, context); + return base.EnterMapping(key, value, context, objectSerializer); } } diff --git a/src/WingetCreateCore/WingetCreateCore.csproj b/src/WingetCreateCore/WingetCreateCore.csproj index f4df191a..27da5d7e 100644 --- a/src/WingetCreateCore/WingetCreateCore.csproj +++ b/src/WingetCreateCore/WingetCreateCore.csproj @@ -1,13 +1,32 @@ - + true true Microsoft.WingetCreateCore - net8.0-windows10.0.22000.0 - x86;x64 - win-x86;win-x64 + net8.0 + x64;ARM64 + win-x64;win-arm64;linux-x64;linux-arm64;osx-arm64 true + true + + + + true + true + true + + true + true + + + + true + true + true + + true + true @@ -15,38 +34,72 @@ - + + + - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + - - $(PkgWiX)\tools\sdk\Microsoft.Deployment.WindowsInstaller.dll - - - $(PkgWiX)\tools\sdk\Microsoft.Deployment.WindowsInstaller.Linq.dll - + + msi.dll + PreserveNewest + + + msi.dll + PreserveNewest + + + libmsi.so + PreserveNewest + + + libmsi.so + PreserveNewest + + + libmsi.dylib + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + @@ -58,7 +111,7 @@ - + diff --git a/src/WingetCreatePackage/WingetCreatePackage.wapproj b/src/WingetCreatePackage/WingetCreatePackage.wapproj index 0a2b4410..6f035c8b 100644 --- a/src/WingetCreatePackage/WingetCreatePackage.wapproj +++ b/src/WingetCreatePackage/WingetCreatePackage.wapproj @@ -4,13 +4,13 @@ 15.0 - + Debug - x86 + ARM64 - + Release - x86 + ARM64 Debug @@ -27,12 +27,12 @@ ac37dfd2-1332-4282-b373-8dcf8bb4e3ba - 10.0.22000.0 + 10.0.26100.0 10.0.17763.0 en-US ..\WingetCreateCLI\WingetCreateCLI.csproj True - x86|x64 + arm64|x64 Always False True @@ -47,7 +47,7 @@ True Properties\PublishProfiles\x64ReleasePublishProfile.pubxml - Properties\PublishProfiles\x86ReleasePublishProfile.pubxml + Properties\PublishProfiles\arm64ReleasePublishProfile.pubxml diff --git a/src/WingetCreateTests/WingetCreateTestExeInstaller/WingetCreateTestExeInstaller.csproj b/src/WingetCreateTests/WingetCreateTestExeInstaller/WingetCreateTestExeInstaller.csproj index 0434aa0e..4a9cfaa5 100644 --- a/src/WingetCreateTests/WingetCreateTestExeInstaller/WingetCreateTestExeInstaller.csproj +++ b/src/WingetCreateTests/WingetCreateTestExeInstaller/WingetCreateTestExeInstaller.csproj @@ -5,15 +5,15 @@ Microsoft Corporation Microsoft Copyright Exe - net6.0 + net8.0 Test exe installer for WingetCreateCLI Microsoft Corporation Microsoft Corporation false false OnOutputUpdated - x86;x64 - win-x86;win-x64 + x64;ARM64 + win-arm64;win-x64 diff --git a/src/WingetCreateTests/WingetCreateTestMsiInstaller/WingetCreateTestMsiInstaller.vdproj b/src/WingetCreateTests/WingetCreateTestMsiInstaller/WingetCreateTestMsiInstaller.vdproj index 6901c3c8..ffc0e819 100644 --- a/src/WingetCreateTests/WingetCreateTestMsiInstaller/WingetCreateTestMsiInstaller.vdproj +++ b/src/WingetCreateTests/WingetCreateTestMsiInstaller/WingetCreateTestMsiInstaller.vdproj @@ -15,7 +15,7 @@ { "Entry" { - "MsmKey" = "8:_EC00637427584C7AB0F79A482C711EFD" + "MsmKey" = "8:_D60286F3493A473D9422AF58A76C74B4" "OwnerKey" = "8:_UNDEFINED" "MsmSig" = "8:_UNDEFINED" } @@ -79,6 +79,14 @@ "PrivateKeyFile" = "8:" "TimeStampServer" = "8:" "InstallerBootstrapper" = "3:2" + "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" + { + "Enabled" = "11:TRUE" + "PromptEnabled" = "11:TRUE" + "PrerequisitesLocation" = "2:1" + "Url" = "8:" + "ComponentsUrl" = "8:" + } } } "Deployable" @@ -686,9 +694,9 @@ } "ProjectOutput" { - "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_EC00637427584C7AB0F79A482C711EFD" + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_D60286F3493A473D9422AF58A76C74B4" { - "SourcePath" = "8:..\\WingetCreateTestExeInstaller\\obj\\Debug\\apphost.exe" + "SourcePath" = "8:..\\WingetCreateTestExeInstaller\\obj\\ARM64\\Release\\apphost.exe" "TargetName" = "8:" "Tag" = "8:" "Folder" = "8:_8B529A5A2AFA41A9A16673642F74D5C8" @@ -708,7 +716,7 @@ "ProjectOutputGroupRegister" = "3:1" "OutputConfiguration" = "8:" "OutputGroupCanonicalName" = "8:PublishItems" - "OutputProjectGuid" = "8:{D61EDBD2-637B-4B5A-848E-2B1A505BE883}" + "OutputProjectGuid" = "8:{B7B1E135-E27C-4189-9C12-95BC387EF130}" "ShowKeyOutput" = "11:TRUE" "ExcludeFilters" { diff --git a/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj b/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj index e366a33e..3b29c7ac 100644 --- a/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj +++ b/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj @@ -4,13 +4,13 @@ 15.0 - + Debug - x86 + ARM64 - + Release - x86 + ARM64 Debug @@ -20,14 +20,6 @@ Release x64 - - Debug - AnyCPU - - - Release - AnyCPU - @@ -39,46 +31,17 @@ 154eb646-d902-41b5-9ce1-e78acc63aa0a - 10.0.22000.0 + 10.0.26100.0 10.0.17763.0 en-US False ..\WingetCreateTestExeInstaller\WingetCreateTestExeInstaller.csproj False - x86|x64 + arm64|x64 SHA256 False True 0 - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - Always diff --git a/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs b/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs index dce082c0..4569076d 100644 --- a/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs +++ b/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs @@ -5,6 +5,7 @@ namespace Microsoft.WingetCreateE2ETests { using System; using System.IO; + using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.WingetCreateCLI.Commands; using Microsoft.WingetCreateCLI.Logging; @@ -117,7 +118,15 @@ await mergeRetryPolicy.ExecuteAsync(async () => string pathToValidate = Path.Combine(Directory.GetCurrentDirectory(), Utils.GetAppManifestDirPath(packageId, PackageVersion)); (bool success, string message) = WinGetUtil.ValidateManifest(pathToValidate); - ClassicAssert.IsTrue(success, message); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + ClassicAssert.IsTrue(success, "Manifest validation is expected to succeed on Windows"); + } + else + { + ClassicAssert.IsFalse(success, "Manifest validation is skipped on non-Windows platforms"); + ClassicAssert.AreEqual(Constants.ManifestValidationUnavailable, message); + } await this.gitHub.ClosePullRequest(updateCommand.PullRequestNumber); } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs index 97d69c68..2e650a70 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs @@ -5,6 +5,7 @@ namespace Microsoft.WingetCreateUnitTests { using System; using System.IO; + using System.Runtime.InteropServices; using Microsoft.WingetCreateCLI; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -20,14 +21,16 @@ public class CommonTests [Test] public void VerifyPathSubstitutions() { - string examplePath = "\\foo\\bar\\baz"; - string path1 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + examplePath; + // Replace with the directory separator character for the current platform (e.g. '/' on Unix, '\' on Windows). + string examplePath = "\\foo\\bar\\baz".Replace("\\", Path.DirectorySeparatorChar.ToString()); + string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string path1 = userProfilePath + examplePath; string path2 = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + examplePath; string path3 = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar) + examplePath; - string substitutedPath1 = "%USERPROFILE%" + examplePath; - string substitutedPath2 = "%LOCALAPPDATA%" + examplePath; - string substitutedPath3 = "%TEMP%" + examplePath; + string substitutedPath1 = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "%USERPROFILE%" : "~") + examplePath; + string substitutedPath2 = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "%LOCALAPPDATA%" + examplePath : path2.Replace(userProfilePath, "~"); + string substitutedPath3 = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "%TEMP%" + examplePath : path3; ClassicAssert.AreEqual(substitutedPath1, Common.GetPathForDisplay(path1, true), "The path does not contain the expected substitutions."); ClassicAssert.AreEqual(path1, Common.GetPathForDisplay(path1, false), "The path should not contain any substitutions."); diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 80a5e1d6..e2ed9246 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -1,10 +1,14 @@  - net8.0-windows10.0.22000.0 false true - x64;x86 + x64;ARM64 + + net8.0-windows10.0.26100.0 + net8.0 @@ -12,10 +16,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive