From 07a73daf5560ce057807bb34a79798485f81e7cd Mon Sep 17 00:00:00 2001 From: Vedant <83997633+vedantmgoyal9@users.noreply.github.com> Date: Sun, 23 Jun 2024 01:05:11 +0530 Subject: [PATCH 1/2] Cross-platform support --- .github/workflows/nightly.yml | 92 +++++ .gitignore | 5 +- .gitmodules | 6 + CONTRIBUTING.md | 2 +- README.md | 12 +- src/WingetCreateCLI.sln | 64 +-- src/WingetCreateCLI/Commands/BaseCommand.cs | 15 +- .../Commands/SettingsCommand.cs | 29 +- src/WingetCreateCLI/Common.cs | 20 +- src/WingetCreateCLI/GitHubOAuth.cs | 12 +- src/WingetCreateCLI/Logger/Logger.cs | 3 +- src/WingetCreateCLI/Models/SettingsModel.cs | 290 +++++++------- .../arm64ReleasePublishProfile.pubxml | 18 + .../x64ReleasePublishProfile.pubxml | 12 +- .../x86ReleasePublishProfile.pubxml | 18 - .../Properties/Resources.Designer.cs | 9 + src/WingetCreateCLI/Properties/Resources.resx | 9 +- src/WingetCreateCLI/WingetCreateCLI.csproj | 175 ++++---- src/WingetCreateCore/Common/Constants.cs | 5 + src/WingetCreateCore/Common/Msi/Msi.cs | 180 +++++++++ src/WingetCreateCore/Common/Msi/rust-msi | 1 + src/WingetCreateCore/Common/MsixAndAppx.cs | 147 +++++++ src/WingetCreateCore/Common/PackageParser.cs | 378 ++++++++---------- src/WingetCreateCore/Common/Utils.cs | 5 +- src/WingetCreateCore/Common/WinGetUtil.cs | 20 + src/WingetCreateCore/WingetCreateCore.csproj | 177 +++++--- .../WingetCreatePackage.wapproj | 14 +- .../WingetCreateTestExeInstaller.csproj | 6 +- .../WingetCreateTestMsixInstaller.wapproj | 38 +- .../WingetCreateTests.csproj | 46 +-- 30 files changed, 1154 insertions(+), 654 deletions(-) create mode 100644 .github/workflows/nightly.yml create mode 100644 .gitmodules create mode 100644 src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml delete mode 100644 src/WingetCreateCLI/Properties/PublishProfiles/x86ReleasePublishProfile.pubxml create mode 100644 src/WingetCreateCore/Common/Msi/Msi.cs create mode 160000 src/WingetCreateCore/Common/Msi/rust-msi create mode 100644 src/WingetCreateCore/Common/MsixAndAppx.cs diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..1fcf2ffd --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,92 @@ +name: Nightly Build 🌙🛠️ + +on: + push: + branches: main + paths: + - .github/workflows/nightly.yml + - src/** + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + build: + strategy: + matrix: + include: + - os: windows-latest + dotnet-runtime: win-x64 + rust-target: x86_64-pc-windows-msvc + - os: windows-latest + dotnet-runtime: win-arm64 + rust-target: aarch64-pc-windows-msvc + - os: ubuntu-latest + dotnet-runtime: linux-x64 + rust-target: x86_64-unknown-linux-gnu + - os: ubuntu-latest + dotnet-runtime: linux-arm64 + rust-target: aarch64-unknown-linux-gnu + - os: macos-latest + dotnet-runtime: osx-arm64 + rust-target: aarch64-apple-darwin + bin-suffix: macos-arm64 + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + with: + submodules: true + lfs: true + persist-credentials: false + + - uses: moonrepo/setup-rust@v1 + with: + channel: stable + targets: ${{ matrix.rust-target }} + cache: false + + - if: matrix.rust-target == 'aarch64-unknown-linux-gnu' + run: | + sudo sed -i -e 's/deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/arm64.list + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/arm64.list + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/arm64.list + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/arm64.list + sudo dpkg --add-architecture arm64 + sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu + + - run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER="aarch64-linux-gnu-gcc" cargo build -r --target ${{ matrix.rust-target }} + working-directory: src/WingetCreateCore/Common/Msi/rust-msi + + - run: dotnet publish ./WingetCreateCLI.csproj -c Release -r ${{ matrix.dotnet-runtime }} --output ./output + working-directory: src/WingetCreateCLI + + - if: matrix.os != 'windows-latest' + run: mv src/WingetCreateCLI/output/WingetCreateCLI ./wingetcreate-${{ matrix.bin-suffix || matrix.dotnet-runtime }} + + - if: matrix.os == 'windows-latest' + run: mv src/WingetCreateCLI/output/WingetCreateCLI.exe ./wingetcreate-${{ matrix.dotnet-runtime }}.exe + + - name: Delete old release 🗑️ + run: | + gh api repos/$GITHUB_REPOSITORY/git/refs/tags/nightly | jq -r '.object.sha' | grep -q $GITHUB_SHA && exit 0 + gh release delete nightly -y --cleanup-tag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: softprops/action-gh-release@v2 + with: + name: Nightly release 🌙 + tag_name: nightly + draft: false + files: wingetcreate-* + fail_on_unmatched_files: true + token: ${{ secrets.GITHUB_TOKEN }} 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..a44b81dd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f6c8d83..fdfea086 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Below is our guidance for how to report issues, propose new features, and submit ## Open Development Workflow -The Windows Package Manager Manifest Creator team is VERY active in this GitHub Repository. In fact, we live in it all day long and carry out all our development in the open! +The Windows Package Manager team is VERY active in this GitHub Repository. In fact, we live in it all day long and carry out all our development in the open! When the team finds issues we file them in the repository. When we propose new ideas or think-up new features, we file new feature requests. When we work on fixes or features, we create branches and work on those improvements. And when PRs are reviewed, we review in public - including all the good, the bad, and the ugly parts. diff --git a/README.md b/README.md index 60f9f88f..a6eb0ee3 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,14 @@ 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' + version: '8.x' installationPath: '$(ProgramFiles)\dotnet' ``` @@ -87,7 +87,7 @@ 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] @@ -158,11 +158,11 @@ You can install the prerequisites in one of two ways: * 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 11 SDK (10.0.22621.0) (Tools -> Get Tools and Features -> Individual Components) ### 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 @@ -181,7 +181,7 @@ Running unit and E2E tests are a great way to ensure that functionality is prese * 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 +* 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] diff --git a/src/WingetCreateCLI.sln b/src/WingetCreateCLI.sln index e7a47962..da35b478 100644 --- a/src/WingetCreateCLI.sln +++ b/src/WingetCreateCLI.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30621.155 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34607.119 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WingetCreateCLI", "WingetCreateCLI\WingetCreateCLI.csproj", "{C36B8829-36E2-44BB-942C-00F29FCAE973}" EndProject @@ -24,68 +24,68 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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|arm64.ActiveCfg = Debug|arm64 + {C36B8829-36E2-44BB-942C-00F29FCAE973}.Debug|arm64.Build.0 = Debug|arm64 {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|arm64.ActiveCfg = Release|arm64 + {C36B8829-36E2-44BB-942C-00F29FCAE973}.Release|arm64.Build.0 = Release|arm64 {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|arm64.ActiveCfg = Debug|arm64 + {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Debug|arm64.Build.0 = Debug|arm64 {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|arm64.ActiveCfg = Release|arm64 + {D61EDBD2-637B-4B5A-848E-2B1A505BE883}.Release|arm64.Build.0 = Release|arm64 {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|arm64.ActiveCfg = Debug|arm64 + {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Debug|arm64.Build.0 = Debug|arm64 {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|arm64.ActiveCfg = Release|arm64 + {90EB2100-7BDB-4FFC-B657-3A63EEED93F9}.Release|arm64.Build.0 = Release|arm64 {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|arm64.ActiveCfg = Debug|arm64 + {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Debug|arm64.Build.0 = Debug|arm64 {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|arm64.ActiveCfg = Release|arm64 + {5613C196-3D8B-4B5C-9288-B24B01DDA3BE}.Release|arm64.Build.0 = Release|arm64 {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 + {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 + {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 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..c6550da3 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,7 +84,7 @@ private void DisplayParsingErrors(List errors, string path) } } - private bool OpenJsonFile(string path) + private static bool OpenJsonFile(string path) { if (!File.Exists(path)) { @@ -92,7 +93,23 @@ private bool OpenJsonFile(string path) try { - Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = path }); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = path }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", path); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", path); + } + else + { + throw new PlatformNotSupportedException(); + } + return true; } catch (Win32Exception e) diff --git a/src/WingetCreateCLI/Common.cs b/src/WingetCreateCLI/Common.cs index e3c5b395..f1754b5e 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 const string TempDirectoryUnix = "/tmp"; private static readonly Lazy AppStatePathLazy = new(() => { - string path = IsRunningAsUwp() + string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".wingetcreate"); +#if WINDOWS + path = IsRunningAsUwp() ? ApplicationData.Current.LocalFolder.Path : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", ModuleName); +#endif Directory.CreateDirectory(path); return path; }); @@ -83,7 +91,9 @@ public static string GetPathForDisplay(string path, bool substituteEnvironmentVa if (path.StartsWith(tempPath, StringComparison.OrdinalIgnoreCase)) { - return path.Replace(tempPath, TempEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? path.Replace(tempPath, TempEnvironmentVariable, StringComparison.OrdinalIgnoreCase) + : path.Replace(tempPath, TempDirectoryUnix, StringComparison.OrdinalIgnoreCase); } else if (path.StartsWith(localAppDataPath, StringComparison.OrdinalIgnoreCase)) { @@ -91,16 +101,20 @@ public static string GetPathForDisplay(string path, bool substituteEnvironmentVa } else if (path.StartsWith(userProfilePath, StringComparison.OrdinalIgnoreCase)) { - return path.Replace(userProfilePath, UserProfileEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? path.Replace(userProfilePath, UserProfileEnvironmentVariable, StringComparison.OrdinalIgnoreCase) + : path.Replace(userProfilePath, UserHomeDirectoryShortcutUnix, StringComparison.OrdinalIgnoreCase); } 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/Models/SettingsModel.cs b/src/WingetCreateCLI/Models/SettingsModel.cs index 47f7f25f..7364ff5a 100644 --- a/src/WingetCreateCLI/Models/SettingsModel.cs +++ b/src/WingetCreateCLI/Models/SettingsModel.cs @@ -1,146 +1,146 @@ -//---------------------- -// -// Generated using the NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) -// -//---------------------- - - -namespace Microsoft.WingetCreateCLI.Models.Settings -{ - #pragma warning disable // Disable all warnings - - /// - /// Telemetry settings - /// - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class Telemetry - { - /// - /// Controls whether telemetry events are written - /// - [Newtonsoft.Json.JsonProperty("disable", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool Disable { get; set; } = false; - - - } - - /// - /// Controls the clean up interval of installer cache and logs - /// - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class CleanUp - { - /// - /// Controls the interval in days for clean up of old files and folders - /// - [Newtonsoft.Json.JsonProperty("intervalInDays", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [System.ComponentModel.DataAnnotations.Range(1, int.MaxValue)] - public int IntervalInDays { get; set; } = 7; - - /// - /// Controls whether clean up is disabled - /// - [Newtonsoft.Json.JsonProperty("disable", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool Disable { get; set; } = false; - - - } - - /// - /// Windows Package Manager Repository settings - /// - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class WindowsPackageManagerRepository - { - /// - /// Specifies the name of the Windows Package Manager Repository owner - /// - [Newtonsoft.Json.JsonProperty("owner", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Owner { get; set; } = "microsoft"; - - /// - /// Specifies the name of the Windows Package Manager Repository - /// - [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Name { get; set; } = "winget-pkgs"; - - - } - - /// - /// Output manifest settings - /// - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class Manifest - { - /// - /// Specifies the format of the manifest file - /// - [Newtonsoft.Json.JsonProperty("format", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public ManifestFormat Format { get; set; } = Microsoft.WingetCreateCLI.Models.Settings.ManifestFormat.Yaml; - - - } - - /// - /// Visual settings - /// - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class Visual - { - /// - /// Controls whether paths displayed on the console are substituted with environment variables - /// - [Newtonsoft.Json.JsonProperty("anonymizePaths", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool AnonymizePaths { get; set; } = true; - - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class SettingsManifest - { - /// - /// The settings json schema - /// - [Newtonsoft.Json.JsonProperty("$schema", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Schema { get; set; } = "https://aka.ms/wingetcreate-settings.schema.0.1.json"; - - [Newtonsoft.Json.JsonProperty("Telemetry", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [System.ComponentModel.DataAnnotations.Required] - public Telemetry Telemetry { get; set; } = new Telemetry(); - - [Newtonsoft.Json.JsonProperty("CleanUp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [System.ComponentModel.DataAnnotations.Required] - public CleanUp CleanUp { get; set; } = new CleanUp(); - - [Newtonsoft.Json.JsonProperty("WindowsPackageManagerRepository", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [System.ComponentModel.DataAnnotations.Required] - public WindowsPackageManagerRepository WindowsPackageManagerRepository { get; set; } = new WindowsPackageManagerRepository(); - - [Newtonsoft.Json.JsonProperty("Manifest", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [System.ComponentModel.DataAnnotations.Required] - public Manifest Manifest { get; set; } = new Manifest(); - - [Newtonsoft.Json.JsonProperty("Visual", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [System.ComponentModel.DataAnnotations.Required] - public Visual Visual { get; set; } = new Visual(); - - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] - public enum ManifestFormat - { - - [System.Runtime.Serialization.EnumMember(Value = @"yaml")] - Yaml = 0, - - - [System.Runtime.Serialization.EnumMember(Value = @"json")] - Json = 1, - - - } +//---------------------- +// +// Generated using the NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- + + +namespace Microsoft.WingetCreateCLI.Models.Settings +{ + #pragma warning disable // Disable all warnings + + /// + /// Telemetry settings + /// + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class Telemetry + { + /// + /// Controls whether telemetry events are written + /// + [Newtonsoft.Json.JsonProperty("disable", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool Disable { get; set; } = false; + + + } + + /// + /// Controls the clean up interval of installer cache and logs + /// + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class CleanUp + { + /// + /// Controls the interval in days for clean up of old files and folders + /// + [Newtonsoft.Json.JsonProperty("intervalInDays", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Range(1, int.MaxValue)] + public int IntervalInDays { get; set; } = 7; + + /// + /// Controls whether clean up is disabled + /// + [Newtonsoft.Json.JsonProperty("disable", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool Disable { get; set; } = false; + + + } + + /// + /// Windows Package Manager Repository settings + /// + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class WindowsPackageManagerRepository + { + /// + /// Specifies the name of the Windows Package Manager Repository owner + /// + [Newtonsoft.Json.JsonProperty("owner", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Owner { get; set; } = "microsoft"; + + /// + /// Specifies the name of the Windows Package Manager Repository + /// + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Name { get; set; } = "winget-pkgs"; + + + } + + /// + /// Output manifest settings + /// + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class Manifest + { + /// + /// Specifies the format of the manifest file + /// + [Newtonsoft.Json.JsonProperty("format", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public ManifestFormat Format { get; set; } = Microsoft.WingetCreateCLI.Models.Settings.ManifestFormat.Yaml; + + + } + + /// + /// Visual settings + /// + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class Visual + { + /// + /// Controls whether paths displayed on the console are substituted with environment variables + /// + [Newtonsoft.Json.JsonProperty("anonymizePaths", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool AnonymizePaths { get; set; } = true; + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class SettingsManifest + { + /// + /// The settings json schema + /// + [Newtonsoft.Json.JsonProperty("$schema", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Schema { get; set; } = "https://aka.ms/wingetcreate-settings.schema.0.1.json"; + + [Newtonsoft.Json.JsonProperty("Telemetry", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public Telemetry Telemetry { get; set; } = new Telemetry(); + + [Newtonsoft.Json.JsonProperty("CleanUp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public CleanUp CleanUp { get; set; } = new CleanUp(); + + [Newtonsoft.Json.JsonProperty("WindowsPackageManagerRepository", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public WindowsPackageManagerRepository WindowsPackageManagerRepository { get; set; } = new WindowsPackageManagerRepository(); + + [Newtonsoft.Json.JsonProperty("Manifest", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public Manifest Manifest { get; set; } = new Manifest(); + + [Newtonsoft.Json.JsonProperty("Visual", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public Visual Visual { get; set; } = new Visual(); + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")] + public enum ManifestFormat + { + + [System.Runtime.Serialization.EnumMember(Value = @"yaml")] + Yaml = 0, + + + [System.Runtime.Serialization.EnumMember(Value = @"json")] + Json = 1, + + + } } \ No newline at end of file diff --git a/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml b/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml new file mode 100644 index 00000000..8fb68c6c --- /dev/null +++ b/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + arm64 + bin\ARM64\Release\net8.0-windows10.0.22621.0\win-arm64\publish\ + FileSystem + net8.0-windows10.0.22621.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 7e6b9caf..bbea266b 100644 --- a/src/WingetCreateCLI/Properties/PublishProfiles/x64ReleasePublishProfile.pubxml +++ b/src/WingetCreateCLI/Properties/PublishProfiles/x64ReleasePublishProfile.pubxml @@ -1,18 +1,18 @@  Release x64 - bin\x64\Release\net6.0-windows10.0.22000.0\win-x64\publish\ + bin\x64\Release\net8.0-windows10.0.22621.0\win-x64\publish\ FileSystem - net6.0-windows10.0.22000.0 + net8.0-windows10.0.22621.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 8fc1cfb0..00000000 --- a/src/WingetCreateCLI/Properties/PublishProfiles/x86ReleasePublishProfile.pubxml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Release - x86 - bin\x86\Release\net6.0-windows10.0.22000.0\win-x86\publish\ - FileSystem - net6.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 8cea1df8..8515309a 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -1769,6 +1769,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 0458ff00..2b52ddaa 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 not available on Linux/macOS platforms. + 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 7883849b..a46774fe 100644 --- a/src/WingetCreateCLI/WingetCreateCLI.csproj +++ b/src/WingetCreateCLI/WingetCreateCLI.csproj @@ -1,41 +1,55 @@  - - Exe - net6.0-windows10.0.22000.0 - WingetCreateCLI - Microsoft.WingetCreateCLI - 1.6 - x64;x86 - win-x64;win-x86 - true - TELEMETRYEVENTSOURCE_PUBLIC - true - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + Exe + WingetCreateCLI + Microsoft.WingetCreateCLI + 1.6 + x64;ARM64 + win-x64;win-arm64;linux-x64;linux-arm64;osx-arm64 + true + TELEMETRYEVENTSOURCE_PUBLIC + true + true + false + true + + true + + + + net8.0-windows10.0.22621.0 + + + + net8.0 + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + - - + + - - - - - - Resources.resx - True - True - - - - + + + + + + Resources.resx + True + True + + + + - - %(LinkBase)\%(Filename)%(Extension) - false - - - - Resources.Designer.cs - PublicResXFileCodeGenerator - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - + + %(LinkBase)\%(Filename)%(Extension) + false + + + + Resources.Designer.cs + PublicResXFileCodeGenerator + + + + + + + + + + - - - - - - - - $(TargetFrameworkSDKToolsDirectory)$(PlatformTarget)\ - - + + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/Constants.cs b/src/WingetCreateCore/Common/Constants.cs index 25ecae77..4bda8cba 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..95aee602 --- /dev/null +++ b/src/WingetCreateCore/Common/Msi/rust-msi @@ -0,0 +1 @@ +Subproject commit 95aee6021c77d24f5a2f6d848889462f995459e5 diff --git a/src/WingetCreateCore/Common/MsixAndAppx.cs b/src/WingetCreateCore/Common/MsixAndAppx.cs new file mode 100644 index 00000000..c33f28c4 --- /dev/null +++ b/src/WingetCreateCore/Common/MsixAndAppx.cs @@ -0,0 +1,147 @@ +// 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"; + + /// + /// Gets metadata of APPX/MSIX package or bundle. + /// +#pragma warning disable SA1401 // Fields should be private + public Metadata Information; +#pragma warning restore SA1401 // Fields should be private + + /// + /// 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 + 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 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 4220d314..5bd7b237 100644 --- a/src/WingetCreateCore/Common/PackageParser.cs +++ b/src/WingetCreateCore/Common/PackageParser.cs @@ -5,29 +5,28 @@ 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 Microsoft.Msix.Utils.Logger; + using System.Xml.Linq; + using AsmResolver.PE; + using AsmResolver.PE.Win32Resources; 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. @@ -672,50 +671,31 @@ 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) + private static MachineType? GetMachineType(string binary) { - using var hasher = SHA256.Create(); - return BitConverter.ToString(hasher.ComputeHash(buffer)).Remove("-"); - } + using FileStream stream = File.OpenRead(binary); + using BinaryReader bw = new BinaryReader(stream); + const ushort executableMagicNumber = 0x5a4d; + const int peMagicNumber = 0x00004550; // "PE\0\0" - private static string HashAppxFile(IAppxFile signatureFile) - { - var signatureBytes = StreamUtils.ReadStreamToByteArray(signatureFile.GetStream()); - return HashBytes(signatureBytes); - } + stream.Seek(0, SeekOrigin.Begin); + int magicNumber = bw.ReadUInt16(); + bool isExecutable = magicNumber == executableMagicNumber; - private static MachineType? GetMachineType(string binary) - { - using (FileStream stream = File.OpenRead(binary)) - using (BinaryReader bw = new BinaryReader(stream)) + if (isExecutable) { - const ushort executableMagicNumber = 0x5a4d; - const int peMagicNumber = 0x00004550; // "PE\0\0" + stream.Seek(60, SeekOrigin.Begin); + int headerOffset = bw.ReadInt32(); - stream.Seek(0, SeekOrigin.Begin); - int magicNumber = bw.ReadUInt16(); - bool isExecutable = magicNumber == executableMagicNumber; + stream.Seek(headerOffset, SeekOrigin.Begin); + int signature = bw.ReadInt32(); + bool isPortableExecutable = signature == peMagicNumber; - if (isExecutable) + if (isPortableExecutable) { - stream.Seek(60, SeekOrigin.Begin); - int headerOffset = bw.ReadInt32(); - - stream.Seek(headerOffset, SeekOrigin.Begin); - int signature = bw.ReadInt32(); - bool isPortableExecutable = signature == peMagicNumber; + MachineType machineType = (MachineType)bw.ReadUInt16(); - if (isPortableExecutable) - { - MachineType machineType = (MachineType)bw.ReadUInt16(); - - return GetCompatibleMachineType(machineType); - } + return GetCompatibleMachineType(machineType); } } @@ -724,13 +704,11 @@ private static string HashAppxFile(IAppxFile signatureFile) private static MachineType GetCompatibleMachineType(MachineType type) { - switch (type) + return type switch { - case MachineType.Armv7: - return MachineType.Arm; - default: - return type; - } + MachineType.Armv7 => MachineType.Arm, + _ => type, + }; } /// @@ -767,77 +745,64 @@ private static bool IsCompatibleInstallerType(InstallerType? type1, InstallerTyp private static CompatibilitySet GetCompatibilitySet(InstallerType type) { - switch (type) + return type switch { - case InstallerType.Inno: - case InstallerType.Nullsoft: - case InstallerType.Exe: - case InstallerType.Burn: // Portable is included as a compatible installer type since // they are detected as 'exe' installers. This is to ensure // updating a portable manifest is supported. - case InstallerType.Portable: - return CompatibilitySet.Exe; - case InstallerType.Wix: - case InstallerType.Msi: - return CompatibilitySet.Msi; - case InstallerType.Msix: - case InstallerType.Appx: - return CompatibilitySet.Msix; - default: - return CompatibilitySet.None; - } + InstallerType.Portable or InstallerType.Inno or InstallerType.Nullsoft or InstallerType.Exe or InstallerType.Burn => CompatibilitySet.Exe, + InstallerType.Wix or InstallerType.Msi => CompatibilitySet.Msi, + InstallerType.Msix or InstallerType.Appx => CompatibilitySet.Msix, + _ => CompatibilitySet.None, + }; } private static bool ParseExeInstallerType(string path, Installer baseInstaller, List newInstallers) { 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 xmlBytes = data.CreateReader().ReadToEnd().ToList(); + + // workaround to fix System.Xml.XmlException (Data at the root level is invalid. Line 1, position 1) + // https://stackoverflow.com/questions/17947238/why-data-at-the-root-level-is-invalid-line-1-position-1-for-xml-document + foreach (byte singleByte in Encoding.UTF8.GetPreamble()) { - rc.LoadFrom(path); - string installerType = rc.Manifest.DocumentElement - .GetElementsByTagName("description") - .Cast() - .FirstOrDefault()? - .InnerText? - .Split(' ').First() - .ToLowerInvariant(); - - if (installerType.EqualsIC("wix")) + if (xmlBytes.IndexOf(singleByte) != -1) { - // 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; + xmlBytes.RemoveAt(xmlBytes.IndexOf(singleByte)); } } - catch (Win32Exception err) + + var manifestXml = new XmlDocument(); + manifestXml.LoadXml(Encoding.UTF8.GetString(xmlBytes.ToArray())); + string installerType = manifestXml.DocumentElement + .GetElementsByTagName("description") + .Cast() + .FirstOrDefault()? + .InnerText? + .Split(' ').First() + .ToLowerInvariant(); + + if (installerType.EqualsIC("wix")) { - 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; - } + // 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; } SetInstallerType(baseInstaller, installerTypeEnum.Value); @@ -848,73 +813,59 @@ 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 { - using (var database = new QDatabase(path, Deployment.WindowsInstaller.DatabaseOpenMode.ReadOnly)) - { - InstallerType installerType = IsWix(database) - ? InstallerType.Wix - : InstallerType.Msi; - SetInstallerType(baseInstaller, installerType); + Msi msiInfo = new Msi(path); + Msi.MsiTable propertyTable = msiInfo.Tables.Where(table => table.Name == "Property").First(); - var properties = database.Properties.ToList(); + 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"); - 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; - } + SetInstallerType(baseInstaller, isWix ? InstallerType.Wix : InstallerType.Msi); - baseInstaller.ProductCode = properties.FirstOrDefault(p => p.Property == "ProductCode")?.Value; + 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('"'); + } - string archString = database.SummaryInfo.Template.Split(';').First(); + baseInstaller.ProductCode = propertyTable.Rows.Where(row => row[0] == "\"ProductCode\"").First()[1].Trim('"'); - archString = archString.EqualsIC("Intel") ? "x86" : - archString.EqualsIC("Intel64") ? "x64" : - archString.EqualsIC("Arm64") ? "Arm64" : - archString.EqualsIC("Arm") ? "Arm" : archString; + string archString = msiInfo.Information.Architecture; - baseInstaller.Architecture = archString.ToEnumOrDefault() ?? Architecture.Neutral; + archString = archString.EqualsIC("Intel") ? "x86" : + archString.EqualsIC("Intel64") ? "x64" : + archString.EqualsIC("Arm64") ? "Arm64" : + archString.EqualsIC("Arm") ? "Arm" : archString; - if (baseInstaller.InstallerLocale == null) - { - string languageString = properties.FirstOrDefault(p => p.Property == "ProductLanguage")?.Value; + baseInstaller.Architecture = archString.ToEnumOrDefault() ?? Architecture.Neutral; - if (int.TryParse(languageString, out int lcid)) + if (baseInstaller.InstallerLocale == null) + { + string languageString = propertyTable.Rows.Where(row => row[0] == "\"ProductLanguage\"").First()[1].Trim('"'); + + if (int.TryParse(languageString, out int lcid)) + { + try { - 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. - } + baseInstaller.InstallerLocale = new CultureInfo(lcid).Name; + } + catch (Exception ex) when (ex is ArgumentOutOfRangeException || ex is CultureNotFoundException) + { + // If the lcid value is invalid, do nothing. } } } @@ -923,9 +874,9 @@ private static bool ParseMsi(string path, Installer baseInstaller, Manifests man return true; } - catch (Deployment.WindowsInstaller.InstallerException) + catch { - // Binary wasn't an MSI, skip + // Binary isn't a valid MSI or wasn't an MSI, skip return false; } } @@ -935,8 +886,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; @@ -944,55 +895,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; @@ -1003,41 +914,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); - IAppxFile signatureFile = bundle.AppxBundleReader.GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE.APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE); - signatureSha256 = HashAppxFile(signatureFile); + using MemoryStream appxSignatureStream = new MemoryStream(); + zipArchive.GetEntry(MsixOrAppxPackage.SignatureFile).Open().CopyTo(appxSignatureStream); + signatureSha256 = MsixOrAppxPackage.GetSignatureSha256(appxSignatureStream); + + // /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; @@ -1047,17 +978,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/WingetCreateCore.csproj b/src/WingetCreateCore/WingetCreateCore.csproj index 49f61d04..a512ef11 100644 --- a/src/WingetCreateCore/WingetCreateCore.csproj +++ b/src/WingetCreateCore/WingetCreateCore.csproj @@ -1,64 +1,141 @@  - - true - true - Microsoft.WingetCreateCore - net6.0-windows10.0.22000.0 - x86;x64 - win-x86;win-x64 - true - - - - - - - - - - - + + true + true + Microsoft.WingetCreateCore + x64;ARM64 + win-x64;win-arm64;linux-x64;linux-arm64;osx-arm64 + true + true + + + + true + true + true + + true + true + + + + true + true + true + + true + true + + + + net8.0-windows10.0.22621.0 + + + + net8.0 + + + + + + + + + + + - - - - 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 - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + msi.dll + PreserveNewest + + + libmsi.so + PreserveNewest + + + libmsi.dylib + PreserveNewest + + + + + msi.dll + PreserveNewest + + + msi.dll + PreserveNewest + + + libmsi.so + PreserveNewest + + + libmsi.so + PreserveNewest + + + libmsi.dylib + PreserveNewest + + + + + + + + + + - + - - + + diff --git a/src/WingetCreatePackage/WingetCreatePackage.wapproj b/src/WingetCreatePackage/WingetCreatePackage.wapproj index 0a2b4410..82b51bba 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.22621.0 10.0.17763.0 en-US ..\WingetCreateCLI\WingetCreateCLI.csproj True - x86|x64 + x64|arm64 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..c2cc30f7 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/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj b/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj index e366a33e..a1c16f75 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,13 +31,13 @@ 154eb646-d902-41b5-9ce1-e78acc63aa0a - 10.0.22000.0 + 10.0.22621.0 10.0.17763.0 en-US False ..\WingetCreateTestExeInstaller\WingetCreateTestExeInstaller.csproj False - x86|x64 + x64|arm64 SHA256 False True @@ -54,30 +46,12 @@ Always - - Always - - - Always - - - Always - - - Always - - - Always - Always Always - - Always - Always diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 966a9442..5e2bf0f8 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -1,30 +1,30 @@  - - net6.0-windows10.0.22000.0 - false - true - x64;x86 - + + net8.0-windows10.0.22621.0 + false + true + x64;ARM64 + - - - + + + - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + @@ -223,4 +223,4 @@ PreserveNewest - + \ No newline at end of file From 615c2d76a3fc0068f5e837f65720122da5a13c8e Mon Sep 17 00:00:00 2001 From: Vedant <83997633+vedantmgoyal9@users.noreply.github.com> Date: Tue, 15 Oct 2024 01:44:22 +0530 Subject: [PATCH 2/2] Bump dependencies and SDK versions --- README.md | 2 +- .../arm64ReleasePublishProfile.pubxml | 34 +- .../x64ReleasePublishProfile.pubxml | 34 +- src/WingetCreateCLI/WingetCreateCLI.csproj | 214 ++++++----- .../Serializers/YamlSerializer.cs | 12 +- src/WingetCreateCore/WingetCreateCore.csproj | 66 ++-- .../WingetCreatePackage.wapproj | 2 +- .../WingetCreateTestMsixInstaller.wapproj | 180 +++++---- .../WingetCreateTests.csproj | 348 +++++++++--------- 9 files changed, 451 insertions(+), 441 deletions(-) diff --git a/README.md b/README.md index a6eb0ee3..492b9e6a 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ You can install the prerequisites in one of two ways: * The following workloads: * .NET Desktop Development * Universal Windows Platform Development -* Windows 11 SDK (10.0.22621.0) (Tools -> Get Tools and Features -> Individual Components) +* Windows 11 SDK (10.0.26100.0) (Tools -> Get Tools and Features -> Individual Components) ### Building diff --git a/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml b/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml index 8fb68c6c..1a522901 100644 --- a/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml +++ b/src/WingetCreateCLI/Properties/PublishProfiles/arm64ReleasePublishProfile.pubxml @@ -1,18 +1,18 @@ - - - - - Release - arm64 - bin\ARM64\Release\net8.0-windows10.0.22621.0\win-arm64\publish\ - FileSystem - net8.0-windows10.0.22621.0 - win-arm64 - true - false - false - false - + + + + + 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 bbea266b..378349dc 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.22621.0\win-x64\publish\ - FileSystem - net8.0-windows10.0.22621.0 - win-x64 - true - false - false - false - + + + + + Release + x64 + bin\x64\Release\net8.0-windows10.0.26100.0\win-x64\publish\ + FileSystem + net8.0-windows10.0.26100.0 + win-x64 + true + false + false + false + \ No newline at end of file diff --git a/src/WingetCreateCLI/WingetCreateCLI.csproj b/src/WingetCreateCLI/WingetCreateCLI.csproj index a46774fe..51a7db58 100644 --- a/src/WingetCreateCLI/WingetCreateCLI.csproj +++ b/src/WingetCreateCLI/WingetCreateCLI.csproj @@ -1,109 +1,107 @@ - - - - Exe - WingetCreateCLI - Microsoft.WingetCreateCLI - 1.6 - x64;ARM64 - win-x64;win-arm64;linux-x64;linux-arm64;osx-arm64 - true - TELEMETRYEVENTSOURCE_PUBLIC - true - true - false - true - - true - - - - net8.0-windows10.0.22621.0 - - - - net8.0 - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - Resources.resx - True - True - - - - - - - %(LinkBase)\%(Filename)%(Extension) - false - - - - Resources.Designer.cs - PublicResXFileCodeGenerator - - - - - - - - - - - - - - - - - + + + + Exe + WingetCreateCLI + Microsoft.WingetCreateCLI + 1.6 + 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 + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + %(LinkBase)\%(Filename)%(Extension) + false + + + + Resources.Designer.cs + PublicResXFileCodeGenerator + + + + + + + + + + + + + + + + + \ No newline at end of file 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 a45f92ba..9aad69cb 100644 --- a/src/WingetCreateCore/WingetCreateCore.csproj +++ b/src/WingetCreateCore/WingetCreateCore.csproj @@ -20,26 +20,16 @@ - true - true - true - - true - true + true + true + true + + true + true - net8.0-windows10.0.22621.0 + net8.0-windows10.0.26100.0 @@ -52,66 +42,58 @@ - + - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + msi.dll PreserveNewest - + libmsi.so PreserveNewest - + libmsi.dylib PreserveNewest - + msi.dll PreserveNewest - + msi.dll PreserveNewest - + libmsi.so PreserveNewest - + libmsi.so PreserveNewest - + libmsi.dylib PreserveNewest diff --git a/src/WingetCreatePackage/WingetCreatePackage.wapproj b/src/WingetCreatePackage/WingetCreatePackage.wapproj index 82b51bba..5eeab8c0 100644 --- a/src/WingetCreatePackage/WingetCreatePackage.wapproj +++ b/src/WingetCreatePackage/WingetCreatePackage.wapproj @@ -27,7 +27,7 @@ ac37dfd2-1332-4282-b373-8dcf8bb4e3ba - 10.0.22621.0 + 10.0.26100.0 10.0.17763.0 en-US ..\WingetCreateCLI\WingetCreateCLI.csproj diff --git a/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj b/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj index a1c16f75..6457c963 100644 --- a/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj +++ b/src/WingetCreateTests/WingetCreateTestMsixInstaller/WingetCreateTestMsixInstaller.wapproj @@ -1,78 +1,104 @@ - - - - 15.0 - - - - Debug - ARM64 - - - Release - ARM64 - - - Debug - x64 - - - Release - x64 - - - - - - - - $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ - - - - 154eb646-d902-41b5-9ce1-e78acc63aa0a - 10.0.22621.0 - 10.0.17763.0 - en-US - False - ..\WingetCreateTestExeInstaller\WingetCreateTestExeInstaller.csproj - False - x64|arm64 - SHA256 - False - True - 0 - - - Always - - - Always - - - Always - - - Always - - - - Designer - - - - - - - - - - - - - - True - - - + + + + 15.0 + + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + x64 + + + Release + x64 + + + Debug + AnyCPU + + + Release + AnyCPU + + + + + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + 154eb646-d902-41b5-9ce1-e78acc63aa0a + 10.0.26100.0 + 10.0.17763.0 + en-US + False + ..\WingetCreateTestExeInstaller\WingetCreateTestExeInstaller.csproj + False + arm64|x64 + SHA256 + False + True + 0 + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + Designer + + + + + + + + + + + + + + True + + + \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 5e2bf0f8..1b9592bb 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -1,7 +1,7 @@  - net8.0-windows10.0.22621.0 + net8.0-windows10.0.26100.0 false true x64;ARM64 @@ -12,10 +12,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -26,7 +26,7 @@ - + PreserveNewest @@ -45,24 +45,24 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -72,155 +72,155 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + \ No newline at end of file