diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md index 01bb8c2e1e..1f699f3de3 100644 --- a/.github/actions/spelling/README.md +++ b/.github/actions/spelling/README.md @@ -14,4 +14,4 @@ File | Purpose | Format | Info [advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) Note: you can replace any of these files with a directory by the same name (minus the suffix) -and then include multiple files inside that directory (with that suffix) to merge multiple files. +and then include multiple files inside that directory (with that suffix) to merge multiple files together. diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 0776f0427d..6d31c6297c 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -149,6 +149,7 @@ ipmo IPortable ISAPPROVEDFOROUTPUT IUnknown +JDKs langutil lastwritetime LCIDTo @@ -276,6 +277,7 @@ rethrowing roadmap robuffer rowcount +rowids rubengustorage ruleset runas @@ -392,10 +394,11 @@ vso wapproj wcsicmp webpage +websites WHOLECHAIN wil -windbg wincrypt +windbg WINEVENT winget winhttp diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns index b159d41dc0..b5d5f40f8a 100644 --- a/.github/actions/spelling/candidate.patterns +++ b/.github/actions/spelling/candidate.patterns @@ -218,7 +218,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* \bmedium\.com/@?[^/\s"]+/[-\w]+ # microsoft -\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* +\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]* # powerbi \bapp\.powerbi\.com/reportEmbed/[^"' ]* # vs devops @@ -405,7 +405,7 @@ ipfs://[0-9a-zA-Z]{3,} # Punycode \bxn--[-0-9a-z]+ # sha -sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* +sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]* # sha-... -- uses a fancy capture (\\?['"]|")[0-9a-f]{40,}\g{-1} # hex runs @@ -425,6 +425,9 @@ sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* # pki -----BEGIN.*-----END +# pki (base64) +LS0tLS1CRUdJT.* + # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b # hex digits including css/html color classes: @@ -446,11 +449,23 @@ integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} Name\[[^\]]+\]=.* # IServiceProvider / isAThing -(?:\b|_)(?:I|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z]|\b)) +(?:\b|_)(?:(?:ns|)I|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) # crypt (['"])\$2[ayb]\$.{56}\g{-1} +# apache/old crypt +(['"]|)\$+(?:apr|)1\$+.{8}\$+.{22}\g{-1} + +# sha1 hash +\{SHA\}[-a-zA-Z=;:/0-9+]{3,} + +# machine learning (?) +#\b(?i)ml(?=[a-z]{2,}) + +# python +\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,}) + # scrypt / argon \$(?:scrypt|argon\d+[di]*)\$\S+ @@ -463,8 +478,17 @@ Name\[[^\]]+\]=.* # scala modules ("[^"]+"\s*%%?\s*){2,3}"[^"]+" +# container images +image: [-\w./:@]+ + +# Docker images +^\s*FROM\s+\S+:\S+(?:\s+AS\s+\S+|) + +# `docker images` REPOSITORY TAG IMAGE ID CREATED SIZE +\s*\S+/\S+\s+\S+\s+[0-9a-f]{8,}\s+\d+\s+(?:hour|day|week)s ago\s+[\d.]+[KMGT]B + # Intel intrinsics -_mm_\w+ +_mm_(?!dd)\w+ # Input to GitHub JSON content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} @@ -473,15 +497,15 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} # you'll want to remove the `(?=.*?")` suffix. # The `(?=.*?")` suffix should limit the false positives rate # printf -#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"]) +%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA])(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"]) # Alternative printf # %s -%(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"]) +%(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%[^s])\b)(?=.*?['"]) # Python string prefix / binary prefix # Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings -(?[-a-zA-Z=;:/0-9+]{3,}== 0.0.22) \\\w{2,}\{ @@ -629,8 +673,14 @@ TeX/AMS # eslint "varsIgnorePattern": ".+" +# nolint +nolint:\w+ + # Windows short paths -[/\\][^/\\]{5,6}~\d{1,2}[/\\] +[/\\][^/\\]{5,6}~\d{1,2}(?=[/\\]) + +# Windows Resources with accelerators +\b[A-Z]&[a-z]+\b(?!;) # cygwin paths /cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+ @@ -641,12 +691,18 @@ TeX/AMS # alternate printf markers if you run into latex and friends #(? Don't use `can not` when you mean `cannot`. The only time you're likely to see `can not` written as separate words is when the word `can` happens to precede some other phrase that happens to start with `not`. @@ -41,11 +48,26 @@ # - if you encounter such a case, add a pattern for that case to patterns.txt. \b[Cc]an not\b +# Do not use `(click) here` links +# For more information, see: +# * https://www.w3.org/QA/Tips/noClickHere +# * https://webaim.org/techniques/hypertext/link_text +# * https://granicus.com/blog/why-click-here-links-are-bad/ +# * https://heyoka.medium.com/dont-use-click-here-f32f445d1021 +(?:>|\[)(?:(?:click |)here|link|(?:read |)more)(?:) + +# Should be `equals` to `is equal to` +\bequals to\b + # Should be `GitHub` -(?]* -S[Hh][Aa]256: [0-9A-Fa-f]{64} -SHA256::ConvertToBytes\("[0-9A-Fa-f]{64}" -# data urls -data:[a-zA-Z=;,/0-9+-]+ -# uuid: -\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b -# hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#|\%23)[0-9a-fA-FgGrR]{2,}[uU]?[lL]{0,2}\b -"[0-9a-f]{32}" -"[0-9a-f]{64}" -# sha-1 -\b[0-9a-f]{40}\b + El proyecto .* diferentes + # Package family names and package full names \b[-_~.A-Za-z0-9]+_[a-z0-9]{13}\b + # Locales for name normalization \b\p{Lu}{2,3}(?:-(?:CANS|CYRL|LATN|MONG))?-\p{Lu}{2}(?![A-Z])(?:-VALENCIA)?\b + # Azure pipeline tasks - task: .* -# Slash-prefixed patterns -\\native(?![a-z]) -\\Release -/NPH(?![a-z]) -/td(?![a-z]) - -# .gitignore -^\[[\w/[\]*.]*$ - -# URLs -- Added here instead of allow.txt to facilitate wildcarding them as more are added -http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer - # schema regex "pattern": .*$ -# doc/ManifestSpecv1.0.md +# doc/.../manifest.md ^ShortDescription: Le nouveau.*$ # Ignore test patterns GetRestAPIBaseUri\(".*"\) == L".*" -# fabricbot.json -"(?:id|user)": "[-A-Za-z0-9_]*" - -# URL escaped characters -\%[0-9A-F]{2} - # some forms of `any more` are correct \battempt any more\b @@ -67,102 +36,92 @@ GetRestAPIBaseUri\(".*"\) == L".*" 9nblggh4nns1 # Automatically suggested patterns -# hit-count: 407 file-count: 78 + +# hit-count: 3076 file-count: 503 # IServiceProvider / isAThing -(?:\b|_)(?:[IT]|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z]|\b)) +(?:\b|_)(?:(?:ns|)I|isA|T)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) -# hit-count: 13 file-count: 9 -# GitHub SHAs (markdown) -(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|) +# hit-count: 1396 file-count: 96 +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b -# Compiler flags -(?:^|[\t ,"'`=(])-[D](?!ata|ebug|ependency|epth|esktop|estination|ev|irectory|ownload) -(?:^|[\t ,"'`=(])-[W](?!ait|arning|in|orking) -(?:^|[\t ,"'`=(])-[l](?!og) -(?:^|[\t ,"'`=(])-[f](?!eatures|ile|ind) - -T(?=h[a-z]{2,}) +# hit-count: 366 file-count: 148 +# hex runs +\b[0-9a-fA-F]{16,}\b -# Automatically suggested patterns -# hit-count: 30 file-count: 4 +# hit-count: 337 file-count: 135 +# microsoft +\b(?:https?://|)(?:(?:(?:apps|blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%?&:#]* + +# hit-count: 296 file-count: 23 # version suffix v# (?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) -# hit-count: 8 file-count: 4 -# libraries -\b(?i)lib(?!elous|erty|rar(?:i(?:an|es)|y))(?=[a-z]) - -# hit-count: 4 file-count: 4 -# Non-English -[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]* - -# hit-count: 3 file-count: 3 -# tar arguments -\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ - -# hit-count: 3 file-count: 3 -# Python string prefix / binary prefix -# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings -(? + { + "cspell": "https://raw.githubusercontent.com/check-spelling/cspell-dicts/v20241114/dictionaries/" + } extra_dictionaries: | - cspell:cpp/src/compiler-msvc.txt - cspell:cpp/src/stdlib-c.txt - cspell:cpp/src/stdlib-cpp.txt + cspell:software-terms/softwareTerms.txt + cspell:cpp/stdlib-cpp.txt cspell:filetypes/filetypes.txt - cspell:fullstack/dict/fullstack.txt - cspell:powershell/dict/powershell.txt - cspell:software-terms/dict/softwareTerms.txt + cspell:cpp/stdlib-c.txt + cspell:php/php.txt + cspell:python/python/python-lib.txt + cspell:dotnet/dotnet.txt + cspell:golang/go.txt + cspell:cpp/compiler-msvc.txt + cspell:dart/dart.txt + cspell:html/html.txt + cspell:powershell/powershell.txt + cspell:aws/aws.txt + cspell:python/common/extra.txt + cspell:node/node.txt + cspell:npm/npm.txt + cspell:fullstack/fullstack.txt + cspell:java/java.txt cspell:csharp/csharp.txt - cspell:dotnet/dict/dotnet.txt - check_extra_dictionaries: '' + cspell:cpp/ecosystem.txt + cspell:typescript/typescript.txt + cspell:cpp/lang-keywords.txt comment-pr: name: Report (PR) @@ -124,7 +140,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.22 + uses: check-spelling/check-spelling@v0.0.24 with: checkout: true task: ${{ needs.spelling.outputs.followup }} diff --git a/README.md b/README.md index 91147c7a07..244240e60c 100644 --- a/README.md +++ b/README.md @@ -1,146 +1,146 @@ -# ![WinGet Icon](.github/images/WindowsPackageManager_Assets/ICO/PNG/_40.png) Windows Package Manager - -## WinGet Client - -![winget install wingetcreate](.github/images/WingetInstall.gif) - -If you are new to the Windows Package Manager, you might want to [Explore the Windows Package Manager tool](https://docs.microsoft.com/learn/modules/explore-windows-package-manager-tool/?WT.mc_id=AZ-MVP-5004737). The client has access to packages from two default sources. The first is "msstore" the Microsoft Store (free Apps rated "e" for everyone). The second is "winget" the [WinGet community repository](https://github.com/microsoft/winget-pkgs). - -> [!NOTE] -> Group policy may be configured and modify configured sources. Run `winget --info` to see any configured policies. - -## Installing The Client - -> [!NOTE] -> The client requires Windows 10 1809 (build 17763) or later at this time. Windows Server 2019 is not supported as the Microsoft Store is not available nor are updated dependencies. It may be possible to install on Windows Server 2022, this should be considered experimental (not supported) and requires dependencies to be manually installed as well. - -### Microsoft Store [Recommended] - -The client is distributed within the [App Installer](https://apps.microsoft.com/detail/9nblggh4nns1) package. - -### Development Releases - -There are two methods to get development releases: - -* Install a [Windows 10 or Windows 11 Insider](https://insider.windows.com/) build. -* Join the Windows Package Manager Insider program by [signing up](http://aka.ms/winget-InsiderProgram). - -> [!NOTE] -> It may take a few days to get the updated App Installer after you receive e-mail confirmation from joining the Windows Package Manager Insider program. If you decide to install the latest release from GitHub, and you have successfully joined the insider program, you will receive updates when the next development release has been published in the Microsoft Store. - -Once you have received the updated App Installer from the Microsoft Store you should be able to execute `winget features` to see experimental features. Some users have reported [issues](https://github.com/microsoft/winget-cli/issues/210) with the client not being on their PATH. - -### Manually Update - -The same Microsoft Store package will be made available via our [Releases](https://github.com/microsoft/winget-cli/releases). Note that installing this package will give you the WinGet client, but it will not enable automatic updates from the Microsoft Store if you have not joined the Windows Package Manager Insider program. - -> [!NOTE] -> You may need to install the [VC++ v14 Desktop Framework Package](https://docs.microsoft.com/troubleshoot/cpp/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages). -> This should only be necessary on older builds of Windows 10 and only if you get an error about missing framework packages. - -### Troubleshooting - -Please read our [troubleshooting guide](/doc/troubleshooting/README.md). - -## Administrator Considerations - -Installer behavior can be different depending on whether you are running **WinGet** with administrator privileges. - -* When running **WinGet** without administrator privileges, some applications may [require elevation](https://docs.microsoft.com/windows/security/identity-protection/user-account-control/how-user-account-control-works) to install. When the installer runs, Windows will prompt you to [elevate](https://docs.microsoft.com/windows/security/identity-protection/user-account-control/how-user-account-control-works#the-uac-user-experience). If you choose not to elevate, the application will fail to install. - -* When running **WinGet** in an Administrator Command Prompt, you will not see [elevation prompts](https://docs.microsoft.com/windows/security/identity-protection/user-account-control/how-user-account-control-works#the-uac-user-experience) if the application requires it. Always use caution when running your command prompt as an administrator, and only install applications you trust. - -### Build your own - -You can also [build the client yourself](#building-the-client). While the client should be perfectly functional, we are not ready to provide full support for clients running outside of the official distribution mechanisms yet. Feel free to file an [Issue](https://github.com/microsoft/winget-cli/issues/new/choose), but know that it may get lower prioritization. - -## Build Status - -[![Build Status](https://dev.azure.com/shine-oss/winget-cli/_apis/build/status/winget-cli%20Build_Test?branchName=master&label=Main%20Branch%20(Including%20PRs))](https://dev.azure.com/shine-oss/winget-cli/_build/latest?definitionId=10&branchName=master) - -## Windows Package Manager Release Roadmap - -The plan for delivering the next Windows Package Manager release is described and included in our [discussions](https://github.com/microsoft/winget-cli/discussions/2063), and will be updated as the project proceeds. - -## Overview of the Windows Package Manager - -The **Windows Package Manager** is a tool designed to help you quickly and easily discover and install those packages that make your PC environment special. By using the **Windows Package Manager**, from one command, you can install your favorite packages: - -`winget install ` - -## Overview - -### Client Repository - -This winget-cli repository includes the source code designed to build the client. You are encouraged to participate in the development of this client. We have plenty of backlog features in our [Issues](https://github.com/microsoft/winget-cli/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog-Client). You can upvote the ones you want, add more, or even [get started on one.](https://github.com/orgs/microsoft/projects/137) - -### Sources - -The client is built around the concept of sources; a set of packages effectively. Sources provide the ability to discover and retrieve the metadata about the packages so that the client can act on it. - -* The default "winget" source includes packages in the [Windows Package Manager Community Repository](https://github.com/microsoft/winget-pkgs). -* The default "msstore" source includes packages in the Microsoft Store. -* It is also possible to host your own private [REST-based](https://github.com/microsoft/winget-cli-restsource) source. - -## Building the client - -1. Clone the repository -2. Configure your system, please use the [configuration file](.configurations/configuration.dsc.yaml). This can be applied by either: - * [Dev Home](https://github.com/microsoft/devhome)'s machine configuration tool - * WinGet configuration. Run `winget configure .configurations/configuration.dsc.yaml` from the project root so relative paths resolve correctly. -3. Run `vcpkg integrate install` from the Developer Command Prompt for VS 2022. This is a one-time setup step until the configuration file in step 2 is updated to work with vcpkg setup. - -### Prerequisites - -* Windows 10 1809 (17763) or later -* [Developer Mode enabled](https://docs.microsoft.com/windows/uwp/get-started/enable-your-device-for-development) -* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) - * Or use WinGet to install it ;) (although you may need to adjust the workloads via Tools->Get Tools and Features...) -* The following workloads: - * .NET Desktop Development - * Desktop Development with C++ - * Universal Windows Platform Development - * Check [.vsconfig file](.vsconfig) for full components list -* [Windows SDK for Windows 11 (10.0.22000.194)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) -> [!NOTE] -> You can also get it through `winget install Microsoft.WindowsSDK --version 10.0.22000.832` (use --force if you have a newer version installed) or via Visual Studio > Get Tools and Features > Individual Components > Windows 10 SDK (10.0.22000.0) -* The following extensions: - * [Microsoft Visual Studio Installer Projects](https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects) - -### Building - -Open `winget-cli\src\AppInstallerCLI.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. After the build finishes, deploy the solution from Build > Deploy Solution. You can then run the client from the command line using `wingetdev`. - -## Credit - -We would like to thank [Keivan Beigi (@kayone)](https://github.com/kayone) for his work on AppGet which helped us with the initial project direction for Windows Package Manager. - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and do, actually grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. More -information is available in our [CONTRIBUTING.md](/CONTRIBUTING.md) file. - -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information, please refer to the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## Data/Telemetry - -The winget.exe client is instrumented to collect usage and diagnostic (error) data and sends it to Microsoft to help improve the product. - -If you build the client yourself the instrumentation will not be enabled and no data will be sent to Microsoft. - -The winget.exe client respects machine-wide privacy settings and users can opt out on their device, as documented in the Microsoft Windows privacy statement [here](https://support.microsoft.com/help/4468236/diagnostics-feedback-and-privacy-in-windows-10-microsoft-privacy). In addition, you may also explicitly block telemetry using [settings](https://docs.microsoft.com/windows/package-manager/winget/settings) - -In short, to opt out, do one of the following: - -**Windows 11**: Go to `Start`, then select `Settings` > `Privacy & Security` > `Diagnostics & feedback` > `Diagnostic data` and unselect `Send optional diagnostic data`. - -**Windows 10**: Go to `Start`, then select `Settings` > `Privacy` > `Diagnostics & feedback`, and select `Required diagnostic data`. - -See the [privacy statement](PRIVACY.md) for more details. +# ![WinGet Icon](.github/images/WindowsPackageManager_Assets/ICO/PNG/_40.png) Windows Package Manager + +## WinGet Client + +![winget install wingetcreate](.github/images/WingetInstall.gif) + +If you are new to the Windows Package Manager, you might want to [Explore the Windows Package Manager tool](https://docs.microsoft.com/learn/modules/explore-windows-package-manager-tool/?WT.mc_id=AZ-MVP-5004737). The client has access to packages from two default sources. The first is "msstore" the Microsoft Store (free Apps rated "e" for everyone). The second is "winget" the [WinGet community repository](https://github.com/microsoft/winget-pkgs). + +> [!NOTE] +> Group policy may be configured and modify configured sources. Run `winget --info` to see any configured policies. + +## Installing The Client + +> [!NOTE] +> The client requires Windows 10 1809 (build 17763) or later at this time. Windows Server 2019 is not supported as the Microsoft Store is not available nor are updated dependencies. It may be possible to install on Windows Server 2022, this should be considered experimental (not supported) and requires dependencies to be manually installed as well. + +### Microsoft Store [Recommended] + +The client is distributed within the [App Installer](https://apps.microsoft.com/detail/9nblggh4nns1) package. + +### Development Releases + +There are two methods to get development releases: + +* Install a [Windows 10 or Windows 11 Insider](https://insider.windows.com/) build. +* Join the Windows Package Manager Insider program by [signing up](http://aka.ms/winget-InsiderProgram). + +> [!NOTE] +> It may take a few days to get the updated App Installer after you receive e-mail confirmation from joining the Windows Package Manager Insider program. If you decide to install the latest release from GitHub, and you have successfully joined the insider program, you will receive updates when the next development release has been published in the Microsoft Store. + +Once you have received the updated App Installer from the Microsoft Store you should be able to execute `winget features` to see experimental features. Some users have reported [issues](https://github.com/microsoft/winget-cli/issues/210) with the client not being on their PATH. + +### Manually Update + +The same Microsoft Store package will be made available via our [Releases](https://github.com/microsoft/winget-cli/releases). Note that installing this package will give you the WinGet client, but it will not enable automatic updates from the Microsoft Store if you have not joined the Windows Package Manager Insider program. + +> [!NOTE] +> You may need to install the [VC++ v14 Desktop Framework Package](https://docs.microsoft.com/troubleshoot/cpp/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages). +> This should only be necessary on older builds of Windows 10 and only if you get an error about missing framework packages. + +### Troubleshooting + +Please read our [troubleshooting guide](/doc/troubleshooting/README.md). + +## Administrator Considerations + +Installer behavior can be different depending on whether you are running **WinGet** with administrator privileges. + +* When running **WinGet** without administrator privileges, some applications may [require elevation](https://docs.microsoft.com/windows/security/identity-protection/user-account-control/how-user-account-control-works) to install. When the installer runs, Windows will prompt you to [elevate](https://docs.microsoft.com/windows/security/identity-protection/user-account-control/how-user-account-control-works#the-uac-user-experience). If you choose not to elevate, the application will fail to install. + +* When running **WinGet** in an Administrator Command Prompt, you will not see [elevation prompts](https://docs.microsoft.com/windows/security/identity-protection/user-account-control/how-user-account-control-works#the-uac-user-experience) if the application requires it. Always use caution when running your command prompt as an administrator, and only install applications you trust. + +### Build your own + +You can also [build the client yourself](#building-the-client). While the client should be perfectly functional, we are not ready to provide full support for clients running outside of the official distribution mechanisms yet. Feel free to file an [Issue](https://github.com/microsoft/winget-cli/issues/new/choose), but know that it may get lower prioritization. + +## Build Status + +[![Build Status](https://dev.azure.com/shine-oss/winget-cli/_apis/build/status/winget-cli%20Build_Test?branchName=master&label=Main%20Branch%20(Including%20PRs))](https://dev.azure.com/shine-oss/winget-cli/_build/latest?definitionId=10&branchName=master) + +## Windows Package Manager Release Roadmap + +The plan for delivering the next Windows Package Manager release is described and included in our [discussions](https://github.com/microsoft/winget-cli/discussions/2063), and will be updated as the project proceeds. + +## Overview of the Windows Package Manager + +The **Windows Package Manager** is a tool designed to help you quickly and easily discover and install those packages that make your PC environment special. By using the **Windows Package Manager**, from one command, you can install your favorite packages: + +`winget install ` + +## Overview + +### Client Repository + +This winget-cli repository includes the source code designed to build the client. You are encouraged to participate in the development of this client. We have plenty of backlog features in our [Issues](https://github.com/microsoft/winget-cli/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog-Client). You can upvote the ones you want, add more, or even [get started on one.](https://github.com/orgs/microsoft/projects/137) + +### Sources + +The client is built around the concept of sources; a set of packages effectively. Sources provide the ability to discover and retrieve the metadata about the packages so that the client can act on it. + +* The default "winget" source includes packages in the [Windows Package Manager Community Repository](https://github.com/microsoft/winget-pkgs). +* The default "msstore" source includes packages in the Microsoft Store. +* It is also possible to host your own private [REST-based](https://github.com/microsoft/winget-cli-restsource) source. + +## Building the client + +1. Clone the repository +2. Configure your system, please use the [configuration file](.configurations/configuration.dsc.yaml). This can be applied by either: + * [Dev Home](https://github.com/microsoft/devhome)'s machine configuration tool + * WinGet configuration. Run `winget configure .configurations/configuration.dsc.yaml` from the project root so relative paths resolve correctly. +3. Run `vcpkg integrate install` from the Developer Command Prompt for VS 2022. This is a one-time setup step until the configuration file in step 2 is updated to work with vcpkg setup. + +### Prerequisites + +* Windows 10 1809 (17763) or later +* [Developer Mode enabled](https://docs.microsoft.com/windows/uwp/get-started/enable-your-device-for-development) +* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) + * Or use WinGet to install it ;) (although you may need to adjust the workloads via Tools->Get Tools and Features...) +* The following workloads: + * .NET Desktop Development + * Desktop Development with C++ + * Universal Windows Platform Development + * Check [.vsconfig file](.vsconfig) for full components list +* [Windows SDK for Windows 11 (10.0.22000.194)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) +> [!NOTE] +> You can also get it through `winget install Microsoft.WindowsSDK --version 10.0.22000.832` (use --force if you have a newer version installed) or via Visual Studio > Get Tools and Features > Individual Components > Windows 10 SDK (10.0.22000.0) +* The following extensions: + * [Microsoft Visual Studio Installer Projects](https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects) + +### Building + +Open `winget-cli\src\AppInstallerCLI.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. After the build finishes, deploy the solution from Build > Deploy Solution. You can then run the client from the command line using `wingetdev`. + +## Credit + +We would like to thank [Keivan Beigi (@kayone)](https://github.com/kayone) for his work on AppGet which helped us with the initial project direction for Windows Package Manager. + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and do, actually grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. More +information is available in our [CONTRIBUTING.md](/CONTRIBUTING.md) file. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information, please refer to the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Data/Telemetry + +The winget.exe client is instrumented to collect usage and diagnostic (error) data and sends it to Microsoft to help improve the product. + +If you build the client yourself the instrumentation will not be enabled and no data will be sent to Microsoft. + +The winget.exe client respects machine-wide privacy settings and users can opt out on their device, as documented in the [Microsoft Windows privacy statement](https://support.microsoft.com/help/4468236/diagnostics-feedback-and-privacy-in-windows-10-microsoft-privacy). In addition, you may also explicitly block telemetry using [settings](https://docs.microsoft.com/windows/package-manager/winget/settings) + +In short, to opt out, do one of the following: + +**Windows 11**: Go to `Start`, then select `Settings` > `Privacy & Security` > `Diagnostics & feedback` > `Diagnostic data` and unselect `Send optional diagnostic data`. + +**Windows 10**: Go to `Start`, then select `Settings` > `Privacy` > `Diagnostics & feedback`, and select `Required diagnostic data`. + +See the [privacy statement](PRIVACY.md) for more details. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 359568312a..7940112a71 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,7 +19,6 @@ pool: variables: solution: 'src\AppInstallerCLI.sln' - appxPackageDir: '$(Build.ArtifactStagingDirectory)/AppxPackages/' EnableDetectorVcpkg: true # Do not set the build version for a PR build. @@ -64,6 +63,7 @@ jobs: buildOutDir: $(Build.SourcesDirectory)\src\$(buildPlatform)\$(buildConfiguration) buildOutDirAnyCpu: $(Build.SourcesDirectory)\src\AnyCPU\$(buildConfiguration) artifactsDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform) + appxPackageDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform)\AppxPackages packageLayoutDir: $(Build.BinariesDirectory)\WingetPackageLayout steps: @@ -168,14 +168,14 @@ jobs: SourceFolder: '$(packageLayoutDir)' TargetFolder: '$(artifactsDir)\DevPackage' - - task: CopyFiles@2 - displayName: 'Copy Dev Packages' + - task: DeleteFiles@1 + displayName: Clean up Package Layout after copy inputs: - SourceFolder: '$(appxPackageDir)' - TargetFolder: '$(artifactsDir)\AppxPackages' + SourceFolder: '$(packageLayoutDir)' + Contents: '**/*' - task: CopyFiles@2 - displayName: 'Copy native binaries for Microsoft.WinGet.Client (net6)' + displayName: 'Copy native binaries for Microsoft.WinGet.Client (net8)' inputs: SourceFolder: $(buildOutDir) Contents: | @@ -183,7 +183,7 @@ jobs: Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd WindowsPackageManager\WindowsPackageManager.dll UndockedRegFreeWinRT\winrtact.dll - TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net6.0-windows10.0.22000.0\SharedDependencies\$(BuildPlatform) + TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net8.0-windows10.0.22000.0\SharedDependencies\$(BuildPlatform) flattenFolders: true - task: CopyFiles@2 @@ -212,7 +212,7 @@ jobs: inputs: SourceFolder: $(buildOutDirAnyCpu) Contents: | - Microsoft.Management.Configuration.Projection\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.Projection.dll + Microsoft.Management.Configuration.Projection\net8.0-windows10.0.22000.0\Microsoft.Management.Configuration.Projection.dll TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform) flattenFolders: true @@ -237,7 +237,7 @@ jobs: - task: CopyFiles@2 displayName: 'Copy Files: WinGetUtilInterop.UnitTests' inputs: - SourceFolder: '$(Build.SourcesDirectory)\src\WinGetUtilInterop.UnitTests\bin\$(buildPlatform)\$(BuildConfiguration)\net6.0' + SourceFolder: '$(Build.SourcesDirectory)\src\WinGetUtilInterop.UnitTests\bin\$(buildPlatform)\$(BuildConfiguration)\net8.0' TargetFolder: '$(artifactsDir)\WinGetUtilInterop.UnitTests\' CleanTargetFolder: true OverWrite: true diff --git a/doc/Settings.md b/doc/Settings.md index 027c0066ab..2b373c1fe4 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -129,6 +129,10 @@ Some of the settings are duplicated under `preferences` and `requirements`. `pre Any arguments passed on the command line will effectively override the matching `requirement` setting for the duration of that command. +> [!NOTE] +> - These settings are only applied for the `winget install` command. +> - Other commands like `winget configure` are not affected by these settings. + ### Scope The `scope` behavior affects the choice between installing a package for the current user or for the entire machine. The matching parameter is `--scope`, and uses the same values (`user` or `machine`). diff --git a/doc/specs/#163 - Dependencies.md b/doc/specs/#163 - Dependencies.md index db52feeaff..bbb5989364 100644 --- a/doc/specs/#163 - Dependencies.md +++ b/doc/specs/#163 - Dependencies.md @@ -58,7 +58,7 @@ These include other packages. The restriction on these dependencies is that they ### External Dependencies These include dependencies from outside of the source the original package is distributed. In some cases suitable items may exist in the same source, but for licensing or personal preference, no explicit package should be required. One example is Java. Many vendors offer JREs and JDKs so it may be more reasonable to have a user informed, and allow the user to confirm the presence of a dependency. -As a first step the Windows Package Manager should show dependency information to the user, see issue [1012](github.com/microsoft/winget-cli/issues/1012) with [spec](../../dependencies/doc/specs/%231012%20-%20Show%20dependencies.md). +As a first step the Windows Package Manager should show dependency information to the user, see issue [1012](https://github.com/microsoft/winget-cli/issues/1012) with [spec](./#1012%20-%20Show%20dependencies.md). ## UI/UX Design diff --git a/doc/specs/#182 - Support for installation of portable standalone apps.md b/doc/specs/#182 - Support for installation of portable standalone apps.md index 4ed882f418..809d47a74c 100644 --- a/doc/specs/#182 - Support for installation of portable standalone apps.md +++ b/doc/specs/#182 - Support for installation of portable standalone apps.md @@ -212,7 +212,7 @@ Support for channels has not yet been implemented. Some portable applications ma ## Resources -[Portable / Standalone Executables · Discussion #44183 · microsoft/winget-pkgs (github.com)](https://github.com/microsoft/winget-pkgs/discussions/44183) +[Portable / Standalone Executables · Discussion #1887 · microsoft/winget-cli (github.com)](https://github.com/microsoft/winget-cli/discussions/1887) [Application Registration - Win32 Apps](https://docs.microsoft.com/windows/win32/shell/app-registration) diff --git a/doc/specs/#364 - Feature Toggle.md b/doc/specs/#364 - Feature Toggle.md index 781c68fad0..79c9a54c98 100644 --- a/doc/specs/#364 - Feature Toggle.md +++ b/doc/specs/#364 - Feature Toggle.md @@ -11,7 +11,7 @@ For [#364](https://github.com/microsoft/winget-cli/issues/364) ## Abstract -As features are implemented within winget, they may cause disruption to users oblivious to the fact that they are in progress. In order to allow work to be done in master, and distributed to early adopters for their feedback, this spec suggests that settings should be used to control "experimental" features. We use this term [as others have](https://github.com/PowerShell/PowerShell-RFC/blob/master/5-Final/RFC0029-Support-Experimental-Features.md) to mean work in progress that is not yet ready for release to the general audience. +As features are implemented within winget, they may cause disruption to users oblivious to the fact that they are in progress. In order to allow work to be done in master, and distributed to early adopters for their feedback, this spec suggests that settings should be used to control "experimental" features. We use this term [as others have](https://github.com/PowerShell/PowerShell-RFC/blob/master/Final/RFC0029-Support-Experimental-Features.md) to mean work in progress that is not yet ready for release to the general audience. ## Inspiration @@ -93,4 +93,4 @@ This feature enables all larger features in the future to have a phased rollout. ## Resources -[PowerShell Experimental Features RFC](https://github.com/PowerShell/PowerShell-RFC/blob/master/5-Final/RFC0029-Support-Experimental-Features.md) +[PowerShell Experimental Features RFC](https://github.com/PowerShell/PowerShell-RFC/blob/master/Final/RFC0029-Support-Experimental-Features.md) diff --git a/doc/windows/package-manager/package/winget-validation.md b/doc/windows/package-manager/package/winget-validation.md index 89c8ebcb66..a59392adfc 100644 --- a/doc/windows/package-manager/package/winget-validation.md +++ b/doc/windows/package-manager/package/winget-validation.md @@ -1,119 +1,119 @@ -## Validation process - -When you create a pull request, this will start an automation process that validates the manifest and processes your pull request. GitHub labels are used to share progress and allow you to communicate with us. - -## Submission expectations - -All application submissions to the Windows Package Manager repository should be well-behaved and adhere to the [Windows Package Manager policies](./windows-package-manager-policies.md). -Here are some expectations for submissions: - -- The manifest complies with the [schema requirements]("https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema"). -- All URLs in the manifest lead to safe websites. - -- The installer and application are virus free. The package may be identified as malware by mistake. If you believe it is a false positive you can submit the installer to the defender team for - analysis from [here](https://www.microsoft.com/wdsi/filesubmission). - -- The application installs and uninstalls correctly for both administrators and non-administrators. - -- The installer supports non-interactive modes. - -- All manifest entries are accurate and not misleading. - -- The installer comes directly from the publisher\'s website. - -Please see [Windows Package Manager policies](windows-package-manager-policies.md) for a complete list of the policies. - -## Pull request labels - -During validation, we apply a series of labels to our pull request to -communicate progress. Some labels will direct the ISV to take action, -while others will be directed to the Package Manager developers. - -### Status Labels - -The following table describes the possible **status labels** you will -encounter: - -| **Label** | **Details** | -|--------------|-------------| -| | | -| **Azure-Pipeline-Passed** | The manifest has completed the test pass. It is waiting for approval. If no issues are encountered during the test pass it will automatically be approved. If a test fails, it may be flagged for manual review.| -| **Blocking-Issue** | This label indicates that the **Pull Request** cannot be approved because there is a blocking issue. You can often tell what the blocking issue is by the included error label as well. | -| **Needs-Attention** | This label indicates that the **Pull Request** needs to be investigated by the Windows Package Manager development team. This is either due to a test failure that needs manual review, or a comment added to the **Pull Request** by the community. | -| **Needs-Author-Feedback** | Indicates there is a failure with the submission. We will reassign **Pull Request** back to you. If you do not address the issue within 10 days, the bot will close the **pull request**. **Needs-Author-Feedback** labels are typically added when there was a failure with the Pull Request that should be updated, or if the person reviewing the Pull Request has a question. | -| **Validation-Completed** | Indicates that the test pass has been completed successfully and your **Pull Request** will be merged.| - -### Error Labels - -The following table describes the possible **error labels** that will be -encountered. Not all of the error cases will be assigned to the ISV -immediately. Some may trigger manual validation. - - -| **Label** | **Details** | -|--------------|-------------| -||| -| **Binary-Validation-Error** | The application included in this **Pull Request** failed to pass the **Installers Scan** test. This test is designed to ensure that the application installs on all environments without warnings. For further details on this error, see [binary validation errors](binary-validation-errors.md). | -| **Error-Analysis-Timeout** | This label indicates that the **Binary-Validation-Test** test timed out. The **Pull Request** will get assigned to a Windows Package Manager developer to look at it. | -| **Error-Hash-Mismatch** | The submitted manifest could not be processed because the **InstallerSha256** hash provided for the **InstallerURL** did not match. Update the **InstallerSha256** in the **Pull Request** and try again. | -| **Error-Installer-Availability** | The validation service was unable to download the installer. This may be related to Azure IP ranges being blocked, or the installer URL may be incorrect. Check that the **InstallerURL** is correct and try again. If you feel this has failed in error, please add a comment and the **Pull Request** will get assigned to a Windows Package Manager developer to look investigate. | -| **Manifest-Path-Error** | The manifest files must be put into a specific folder structure. This label indicates a problem with the path of your submission. For example, the folder structure does not have the [required format](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema). Update your manifest and path resubmit your **Pull Request**. | -| **Manifest-Validation-Error** | The submitted manifest contains a syntax error. Address the syntax issue with the manifest and re-submit. For details on the manifest format and schema see: [required format](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema). | -| **PullRequest-Error** | The pull request is invalid because not all files submitted are under manifest folder or there is more than one package or version in the **Pull Request**. Update your **Pull Request** to address the issue and try again. | -| **URL-Validation-Error** | The **URLs Validation Test** could not locate the URL and responded with a [HTTP error status code](https://docs.microsoft.com/troubleshoot/iis/http-status-code) (403 or 404), or the URL reputation test failed. You can identify which URL is in question by looking at the [Pull Request check details](winget-validation-troubleshooter.md). To address this issue, update the URLs in question to resolve the [HTTP error status code](https://docs.microsoft.com/troubleshoot/iis/http-status-code). If the issue is not due to [HTTP error status code](https://docs.microsoft.com/troubleshoot/iis/http-status-code) then you can [submit the URL for review](https://www.microsoft.com/wdsi/filesubmission/) to avoid the reputation failure. | -| **Validation-Defender-Error** | During dynamic testing, Defender reported a problem. To reproduce this problem, install your application, then run a Defender full scan. If you can reproduce the problem, either fix the binary, or submit to this URL for false positive assistance. As stated in the following article, [Address false positives/negatives in Microsoft Defender for Endpoint Microsoft Docs](https://docs.microsoft.com/microsoft-365/security/defender-endpoint/defender-endpoint-false-positives-negatives?view=o365-worldwide), you can submit your binary for analysis to the [defender analysis web page](https://docs.microsoft.com/microsoft-365/security/defender-endpoint/defender-endpoint-false-positives-negatives?view=o365-worldwide#part-4-submit-a-file-for-analysis). If you are unable to reproduce, add a comment to get the Windows Package Manager developers to look at it. | -| **Validation-Domain** | The test has determined the domain if the **InstallerURL** does not match the domain expected. The Windows Package Manager policies requires that the [InstallerUrl](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema) comes directly from the ISVs release location. If you believe this is a false detection, add a comment to the **Pull Request** to get the Windows Package Manager developers to look at it. | -| **Validation-Error** | Validation of the Windows Package Manager failed during manual approval. Look at the accompanying comment for next steps. | -| **Validation-Executable-Error** | During installation testing, the test was unable to locate the primary application. Make sure the application installs correctly on all platforms. If your application does not install an application, but should still be included in the repository, add a comment to the **Pull Request** to get the Windows Package Manager developers to look at it.| -| **Validation-Hash-Verification-Failed** | During installation testing, the application fails to install because the **InstallerSha256** no longer matches the **InstallerURL** hash. This can occur if the application is behind a vanity URL and the installer was updated without updating the **InstallerSha256**. To address this issue, update the **InstallerSha256** associated with the **InstallerURL** and submit again. | -| **Validation-HTTP-Error** | The URL used for the installer does not use the HTTPs protocol. Please update the **InstallerURL** to use HTTPS and resubmit the **Pull Request.** | -| **Validation-Indirect-URL** | The URL is not coming directly from the ISVs server. Testing has determined a redirector has been used. This is not allowed because the Windows Package Manager policies require that the [InstallerUrl](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema) comes directly from the ISVs release location. Remove the redirection and resubmit. -| **Validation-Installation-Error** | During manual validation of this package, there was a general error. Look at the accompanying comment for next steps.| -| **Validation-Merge-Conflict** | This package could not be validated due to a merge conflict. Please address the merge conflict and resubmit your **Pull Request.** -| **Validation-MSIX-Dependency** | The MSIX package has a dependency on package that could not be resolved. Update the package to include the missing components or add the dependency to the manifest file and resubmit the **Pull Request.**| -| **Validation-Unapproved-URL** | The test has determined the domain if the **InstallerURL** does not match the domain expected. The Windows Package Manager policies requires that the [InstallerUrl](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema) comes directly from the ISVs release location. | -| **Validation-Unattended-Failed** | During installation, the test timed out.This most likely is due to the application not installing silently. It could also be due to some other error being encountered and stopping the test. Verify that you can install your manifest without user input. If you need assistance, add a comment to the **Pull Request** and the Windows Package Manager developers will look at it. | -| **Validation-Uninstall-Error** | During uninstall testing, the application did not clean up completely following uninstall. Look at the accompanying comment for more details.| -| **Validation-VCRuntime-Dependency** | The package has a dependency on the C++ runtime that could not be resolved. Update the package to include the missing components or add the dependency to the manifest file and resubmit the **Pull Request**. | - -### Content Policy Labels - -The following table lists **content policy labels**. If one of -the following labels is added, then something in the manifest metadata -triggered additional manual content review to ensure that the metadata -is following the [Windows Package Manager policies](windows-package-manager-policies.md). - -| **Label** | **Details** | -|--------------|-------------| -||| -| **Policy-Test-2.1** |Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#21-general-content-requirements) | -| **Policy-Test-2.2** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#22-content-including-names-logos-original-and-third-party) | -| **Policy-Test-2.3** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#23-risk-of-harm) | -| **Policy-Test-2.4** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#24-defamatory-libelous-slanderous-and-threatening) | -| **Policy-Test-2.5** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#25-offensive-content) | -| **Policy-Test-2.6** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#26-alcohol-tobacco-weapons-and-drugs) | -| **Policy-Test-2.7** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#27-adult-content) | -| **Policy-Test-2.8** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#28-illegal-activity) | -| **Policy-Test-2.9** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#29-excessive-profanity-and-inappropriate-content) | -| **Policy-Test-2.10** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#210-countryregion-specific-requirements) | -| **Policy-Test-2.11** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#211-age-ratings) | -| **Policy-Test-2.12** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#212-user-generated-content) | - -### Internal Labels - -The following table lists the **internal errors**. When internal errors are encountered the **Pull Request** will be assigned to the Windows Package -Manager developers to investigate: - -| **Label** | **Details** | -|--------------|-------------| -||| -|**Internal-Error-Domain**|During the domain validation of the URL, the test encountered an issue. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-Dynamic-Scan**|During the validation of the installed binaries, the test encountered an issue. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-Keyword-Policy**| During the validation of the manifest, the test encountered an issue. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-Manifest**| During the validation of the manifest, the test encountered an issue. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-NoArchitectures**| Testing encountered and issue where the test could not determine the architecture if the application. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-NoSupportedArchitectures**| Testing encountered and issue where the current architecture is not supported. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-PR**| An error occurred during the processing of the PR. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-Static-Scan**| During static analysis of the installers, the test encountered an issue. A Windows Package Manager developer will take a look at it.| -|**Internal-Error-URL**| During reputation validation of the installers, the test encountered an issue. A Windows Package Manager developer will take a look at it.| -|**Internal-Error**| This indicates a generic failure or unknown error was encountered during the test pass. A Windows Package Manager developer will take a look at it.| +## Validation process + +When you create a pull request, this will start an automation process that validates the manifest and processes your pull request. GitHub labels are used to share progress and allow you to communicate with us. + +## Submission expectations + +All application submissions to the Windows Package Manager repository should be well-behaved and adhere to the [Windows Package Manager policies](./windows-package-manager-policies.md). +Here are some expectations for submissions: + +- The manifest complies with the [schema requirements](https://learn.microsoft.com/en-us/windows/package-manager/package/manifest?tabs=minschema%2Cversion-example#minimal-required-schema). +- All URLs in the manifest lead to safe websites. + +- The installer and application are virus free. The package may be identified as malware by mistake. If you believe it is a false positive you can [submit the installer to the defender team for + analysis](https://www.microsoft.com/wdsi/filesubmission). + +- The application installs and uninstalls correctly for both administrators and non-administrators. + +- The installer supports non-interactive modes. + +- All manifest entries are accurate and not misleading. + +- The installer comes directly from the publisher\'s website. + +Please see [Windows Package Manager policies](windows-package-manager-policies.md) for a complete list of the policies. + +## Pull request labels + +During validation, we apply a series of labels to our pull request to +communicate progress. Some labels will direct the ISV to take action, +while others will be directed to the Package Manager developers. + +### Status Labels + +The following table describes the possible **status labels** you will +encounter: + +| **Label** | **Details** | +|--------------|-------------| +| | | +| **Azure-Pipeline-Passed** | The manifest has completed the test pass. It is waiting for approval. If no issues are encountered during the test pass it will automatically be approved. If a test fails, it may be flagged for manual review.| +| **Blocking-Issue** | This label indicates that the **Pull Request** cannot be approved because there is a blocking issue. You can often tell what the blocking issue is by the included error label as well. | +| **Needs-Attention** | This label indicates that the **Pull Request** needs to be investigated by the Windows Package Manager development team. This is either due to a test failure that needs manual review, or a comment added to the **Pull Request** by the community. | +| **Needs-Author-Feedback** | Indicates there is a failure with the submission. We will reassign **Pull Request** back to you. If you do not address the issue within 10 days, the bot will close the **pull request**. **Needs-Author-Feedback** labels are typically added when there was a failure with the Pull Request that should be updated, or if the person reviewing the Pull Request has a question. | +| **Validation-Completed** | Indicates that the test pass has been completed successfully and your **Pull Request** will be merged.| + +### Error Labels + +The following table describes the possible **error labels** that will be +encountered. Not all of the error cases will be assigned to the ISV +immediately. Some may trigger manual validation. + + +| **Label** | **Details** | +|--------------|-------------| +||| +| **Binary-Validation-Error** | The application included in this **Pull Request** failed to pass the **Installers Scan** test. This test is designed to ensure that the application installs on all environments without warnings. For further details on this error, see [binary validation errors](binary-validation-errors.md). | +| **Error-Analysis-Timeout** | This label indicates that the **Binary-Validation-Test** test timed out. The **Pull Request** will get assigned to a Windows Package Manager developer to look at it. | +| **Error-Hash-Mismatch** | The submitted manifest could not be processed because the **InstallerSha256** hash provided for the **InstallerURL** did not match. Update the **InstallerSha256** in the **Pull Request** and try again. | +| **Error-Installer-Availability** | The validation service was unable to download the installer. This may be related to Azure IP ranges being blocked, or the installer URL may be incorrect. Check that the **InstallerURL** is correct and try again. If you feel this has failed in error, please add a comment and the **Pull Request** will get assigned to a Windows Package Manager developer to look investigate. | +| **Manifest-Path-Error** | The manifest files must be put into a specific folder structure. This label indicates a problem with the path of your submission. For example, the folder structure does not have the [required format](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema). Update your manifest and path resubmit your **Pull Request**. | +| **Manifest-Validation-Error** | The submitted manifest contains a syntax error. Address the syntax issue with the manifest and re-submit. For details on the manifest format and schema see: [required format](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema). | +| **PullRequest-Error** | The pull request is invalid because not all files submitted are under manifest folder or there is more than one package or version in the **Pull Request**. Update your **Pull Request** to address the issue and try again. | +| **URL-Validation-Error** | The **URLs Validation Test** could not locate the URL and responded with a [HTTP error status code](https://docs.microsoft.com/troubleshoot/iis/http-status-code) (403 or 404), or the URL reputation test failed. You can identify which URL is in question by looking at the [Pull Request check details](winget-validation-troubleshooter.md). To address this issue, update the URLs in question to resolve the [HTTP error status code](https://docs.microsoft.com/troubleshoot/iis/http-status-code). If the issue is not due to [HTTP error status code](https://docs.microsoft.com/troubleshoot/iis/http-status-code) then you can [submit the URL for review](https://www.microsoft.com/wdsi/filesubmission/) to avoid the reputation failure. | +| **Validation-Defender-Error** | During dynamic testing, Defender reported a problem. To reproduce this problem, install your application, then run a Defender full scan. If you can reproduce the problem, either fix the binary, or submit to this URL for false positive assistance. As stated in the following article, [Address false positives/negatives in Microsoft Defender for Endpoint Microsoft Docs](https://docs.microsoft.com/microsoft-365/security/defender-endpoint/defender-endpoint-false-positives-negatives?view=o365-worldwide), you can submit your binary for analysis to the [defender analysis web page](https://docs.microsoft.com/microsoft-365/security/defender-endpoint/defender-endpoint-false-positives-negatives?view=o365-worldwide#part-4-submit-a-file-for-analysis). If you are unable to reproduce, add a comment to get the Windows Package Manager developers to look at it. | +| **Validation-Domain** | The test has determined the domain if the **InstallerURL** does not match the domain expected. The Windows Package Manager policies requires that the [InstallerUrl](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema) comes directly from the ISVs release location. If you believe this is a false detection, add a comment to the **Pull Request** to get the Windows Package Manager developers to look at it. | +| **Validation-Error** | Validation of the Windows Package Manager failed during manual approval. Look at the accompanying comment for next steps. | +| **Validation-Executable-Error** | During installation testing, the test was unable to locate the primary application. Make sure the application installs correctly on all platforms. If your application does not install an application, but should still be included in the repository, add a comment to the **Pull Request** to get the Windows Package Manager developers to look at it.| +| **Validation-Hash-Verification-Failed** | During installation testing, the application fails to install because the **InstallerSha256** no longer matches the **InstallerURL** hash. This can occur if the application is behind a vanity URL and the installer was updated without updating the **InstallerSha256**. To address this issue, update the **InstallerSha256** associated with the **InstallerURL** and submit again. | +| **Validation-HTTP-Error** | The URL used for the installer does not use the HTTPs protocol. Please update the **InstallerURL** to use HTTPS and resubmit the **Pull Request.** | +| **Validation-Indirect-URL** | The URL is not coming directly from the ISVs server. Testing has determined a redirector has been used. This is not allowed because the Windows Package Manager policies require that the [InstallerUrl](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema) comes directly from the ISVs release location. Remove the redirection and resubmit. +| **Validation-Installation-Error** | During manual validation of this package, there was a general error. Look at the accompanying comment for next steps.| +| **Validation-Merge-Conflict** | This package could not be validated due to a merge conflict. Please address the merge conflict and resubmit your **Pull Request.** +| **Validation-MSIX-Dependency** | The MSIX package has a dependency on package that could not be resolved. Update the package to include the missing components or add the dependency to the manifest file and resubmit the **Pull Request.**| +| **Validation-Unapproved-URL** | The test has determined the domain if the **InstallerURL** does not match the domain expected. The Windows Package Manager policies requires that the [InstallerUrl](https://docs.microsoft.com/windows/package-manager/package/manifest?tabs=minschema%2Ccompschema) comes directly from the ISVs release location. | +| **Validation-Unattended-Failed** | During installation, the test timed out.This most likely is due to the application not installing silently. It could also be due to some other error being encountered and stopping the test. Verify that you can install your manifest without user input. If you need assistance, add a comment to the **Pull Request** and the Windows Package Manager developers will look at it. | +| **Validation-Uninstall-Error** | During uninstall testing, the application did not clean up completely following uninstall. Look at the accompanying comment for more details.| +| **Validation-VCRuntime-Dependency** | The package has a dependency on the C++ runtime that could not be resolved. Update the package to include the missing components or add the dependency to the manifest file and resubmit the **Pull Request**. | + +### Content Policy Labels + +The following table lists **content policy labels**. If one of +the following labels is added, then something in the manifest metadata +triggered additional manual content review to ensure that the metadata +is following the [Windows Package Manager policies](windows-package-manager-policies.md). + +| **Label** | **Details** | +|--------------|-------------| +||| +| **Policy-Test-2.1** |Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#21-general-content-requirements) | +| **Policy-Test-2.2** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#22-content-including-names-logos-original-and-third-party) | +| **Policy-Test-2.3** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#23-risk-of-harm) | +| **Policy-Test-2.4** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#24-defamatory-libelous-slanderous-and-threatening) | +| **Policy-Test-2.5** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#25-offensive-content) | +| **Policy-Test-2.6** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#26-alcohol-tobacco-weapons-and-drugs) | +| **Policy-Test-2.7** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#27-adult-content) | +| **Policy-Test-2.8** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#28-illegal-activity) | +| **Policy-Test-2.9** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#29-excessive-profanity-and-inappropriate-content) | +| **Policy-Test-2.10** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#210-countryregion-specific-requirements) | +| **Policy-Test-2.11** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#211-age-ratings) | +| **Policy-Test-2.12** | Manual review triggered see [Windows Package Manager Policies](windows-package-manager-policies.md#212-user-generated-content) | + +### Internal Labels + +The following table lists the **internal errors**. When internal errors are encountered the **Pull Request** will be assigned to the Windows Package +Manager developers to investigate: + +| **Label** | **Details** | +|--------------|-------------| +||| +|**Internal-Error-Domain**|During the domain validation of the URL, the test encountered an issue. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-Dynamic-Scan**|During the validation of the installed binaries, the test encountered an issue. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-Keyword-Policy**| During the validation of the manifest, the test encountered an issue. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-Manifest**| During the validation of the manifest, the test encountered an issue. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-NoArchitectures**| Testing encountered and issue where the test could not determine the architecture if the application. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-NoSupportedArchitectures**| Testing encountered and issue where the current architecture is not supported. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-PR**| An error occurred during the processing of the PR. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-Static-Scan**| During static analysis of the installers, the test encountered an issue. A Windows Package Manager developer will take a look at it.| +|**Internal-Error-URL**| During reputation validation of the installers, the test encountered an issue. A Windows Package Manager developer will take a look at it.| +|**Internal-Error**| This indicates a generic failure or unknown error was encountered during the test pass. A Windows Package Manager developer will take a look at it.| diff --git a/doc/windows/package-manager/winget/export.md b/doc/windows/package-manager/winget/export.md index 423187c013..639ac32d7c 100644 --- a/doc/windows/package-manager/winget/export.md +++ b/doc/windows/package-manager/winget/export.md @@ -1,71 +1,71 @@ ---- -title: export Command -description: exports the list of installed applications. -ms.date: 05/02/2021 -ms.topic: overview -ms.localizationpriority: medium ---- - -# export command (winget) - -The **export** command of the [winget](index.md) tool exports a JSON file of apps to a specified file. The **export** command users JSON as the format. The JSON schema used by **winget** can be found [here](https://aka.ms/winget-packages.schema.1.0.json). - -The **export** combined with the [**import**](import.md) command allows you to batch install applications on your PC. - -The **export** command is often used to create a file that you can share with other developers, or for use when restoring your build environment. - -## Usage - -`winget export [-o] []` - -![export](images/export.png) - -## Arguments - -The following arguments are available. -| Argument | Description | -|-------------|-------------| -| **-o,--output** | Path to the JSON file to be created - -## Options - -The options allow you to customize the export experience to meet your needs. - -| Option | Description | -|--------|-------------| -| **-s, --source** | [optional] Specifies a source to export files from. Use this option when you only want files from a specific source. | -| **--include-versions** | [optional] Includes the version of the app currently installed. Use this option if you want a specific version. By default, unless specified, [**import**](import.md) will use latest. | -| **--accept-source-agreements** | Accept all source agreements during source operations | -| **-?, --help** | Shows help about the selected command | -| **--wait** | Prompts the user to press any key before exiting | -| **--logs, --open-logs** | Open the default logs location | -| **--verbose, --verbose-logs** | Enables verbose logging for winget | -| **--disable-interactivity** | Disable interactive prompts | - -## JSON Schema -The driving force behind the **export** command is the JSON file. As mentioned, you can find the schema for the JSON file [here](https://aka.ms/winget-packages.schema.1.0.json). - -The JSON file includes the following hierarchy: -| Entry | Description | -|-------------|-------------| -| **Sources** | The sources application manifests come from. | -| **Packages** | The collection of packages to install. | -| **PackageIdentifier** | The Windows Package Manager package identifier used to specify the package. | -| **Version** | [Optional] The specific version of the package to install. | - -## exporting files - -When the Windows Package Manager exports the JSON file, it attempts to export all the applications installed on the PC. If the **winget export** command is not able to match an application to an application from an available **source**, the export command will show a warning. - -Note: matching an application depends on metadata in the manifest from a configured source, and metadata in Add / Remove Programs in Windows based on the package installer. - -In the example below, you will see warnings for **WhatsApp Desktop** and **7-Zip**. - -![export](images/export-command.png) - -Once the export is complete, you can edit the resulting JSON file in your favorite editor. You can remove apps you do not wish to import in the future. - -## Related topics - -* [Use the winget tool to install and manage applications](index.md) - +--- +title: export Command +description: exports the list of installed applications. +ms.date: 05/02/2021 +ms.topic: overview +ms.localizationpriority: medium +--- + +# export command (winget) + +The **export** command of the [winget](index.md) tool exports a JSON file of apps to a specified file. The **export** command users JSON as the format. See [the JSON schema used by **winget**](https://aka.ms/winget-packages.schema.1.0.json). + +The **export** combined with the [**import**](import.md) command allows you to batch install applications on your PC. + +The **export** command is often used to create a file that you can share with other developers, or for use when restoring your build environment. + +## Usage + +`winget export [-o] []` + +![export](images/export.png) + +## Arguments + +The following arguments are available. +| Argument | Description | +|-------------|-------------| +| **-o,--output** | Path to the JSON file to be created + +## Options + +The options allow you to customize the export experience to meet your needs. + +| Option | Description | +|--------|-------------| +| **-s, --source** | [optional] Specifies a source to export files from. Use this option when you only want files from a specific source. | +| **--include-versions** | [optional] Includes the version of the app currently installed. Use this option if you want a specific version. By default, unless specified, [**import**](import.md) will use latest. | +| **--accept-source-agreements** | Accept all source agreements during source operations | +| **-?, --help** | Shows help about the selected command | +| **--wait** | Prompts the user to press any key before exiting | +| **--logs, --open-logs** | Open the default logs location | +| **--verbose, --verbose-logs** | Enables verbose logging for winget | +| **--disable-interactivity** | Disable interactive prompts | + +## JSON Schema +The driving force behind the **export** command is the JSON file. As mentioned, you can see the [schema for the JSON file](https://aka.ms/winget-packages.schema.1.0.json). + +The JSON file includes the following hierarchy: +| Entry | Description | +|-------------|-------------| +| **Sources** | The sources application manifests come from. | +| **Packages** | The collection of packages to install. | +| **PackageIdentifier** | The Windows Package Manager package identifier used to specify the package. | +| **Version** | [Optional] The specific version of the package to install. | + +## exporting files + +When the Windows Package Manager exports the JSON file, it attempts to export all the applications installed on the PC. If the **winget export** command is not able to match an application to an application from an available **source**, the export command will show a warning. + +Note: matching an application depends on metadata in the manifest from a configured source, and metadata in Add / Remove Programs in Windows based on the package installer. + +In the example below, you will see warnings for **WhatsApp Desktop** and **7-Zip**. + +![export](images/export-command.png) + +Once the export is complete, you can edit the resulting JSON file in your favorite editor. You can remove apps you do not wish to import in the future. + +## Related topics + +* [Use the winget tool to install and manage applications](index.md) + diff --git a/doc/windows/package-manager/winget/features.md b/doc/windows/package-manager/winget/features.md index 0ceac6082b..4dc4c53586 100644 --- a/doc/windows/package-manager/winget/features.md +++ b/doc/windows/package-manager/winget/features.md @@ -12,7 +12,7 @@ The **features** command of the [winget](index.md) tool displays a list of the e Each feature can be turned on individually by enabling the features through [**settings**](settings.md). -You can find the latest up to date information on the [experimental features](/doc/Settings.md#experimental-features) web page. +You can find the latest up to date information on the [experimental features](../../../Settings.md#experimental-features) web page. ## Usage diff --git a/doc/windows/package-manager/winget/import.md b/doc/windows/package-manager/winget/import.md index d38c4e6a44..045507dc97 100644 --- a/doc/windows/package-manager/winget/import.md +++ b/doc/windows/package-manager/winget/import.md @@ -1,62 +1,62 @@ ---- -title: import Command -description: imports the list of installed applications. -ms.date: 05/02/2021 -ms.topic: overview -ms.localizationpriority: medium ---- - -# import command (winget) - -The **import** command of the [winget](index.md) tool imports a JSON file of apps to install. The **import** command combined with the [**export**](export.md) command allows you to batch install applications on your PC. - -The **import** command is often used to share your developer environment or build up your PC image with your favorite apps. - -## Usage - -`winget import [-i] []` - -![import](images/import.png) - -## Arguments - -The following arguments are available. -| Argument | Description | -|-------------|-------------| -| **-i, --import-file** | JSON file describing the packages to install - -## Options - -The options allow you to customize the import experience to meet your needs. - -| Option | Description | -|-------------|-------------| -| **--ignore-unavailable** | Suppresses errors if the app requested is unavailable | -| **--ignore-versions** | Ignores versions specified in the JSON file and installs the latest available version | -| **--no-upgrade** | Skips upgrade if an installed version already exists | -| **--accept-package-agreements** | Accept all license agreements for packages | -| **--accept-source-agreements** | Accept all source agreements during source operations | -| **-?, --help** | Shows help about the selected command | -| **--wait** | Prompts the user to press any key before exiting | -| **--logs, --open-logs** | Open the default logs location | -| **--verbose, --verbose-logs** | Enables verbose logging for winget | -| **--disable-interactivity** | Disable interactive prompts | - -## JSON Schema -The driving force behind the **import** command is the JSON file. You can find the schema for the JSON file [here](https://aka.ms/winget-packages.schema.1.0.json). - -The JSON file includes the following hierarchy: -| Entry | Description | -|-------------|-------------| -| **Sources** | The sources application manifests come from. | -| **Packages** | The collection of packages to install. | -| **PackageIdentifier** | The Windows Package Manager package identifier used to specify the package. | -| **Version** | [optional] The specific version of the package to install. | - -## Importing files - -When the Windows Package Manager imports the JSON file, it attempts to install the specified applications in a serial fashion. If the application is not available or the application is already installed, it will notify the user of that case. - -![import](images/import-command.png) - -You will notice in the example above, **Microsoft.VisualStudioCode** and **JanDeDobbeleer.OhMyPosh** were already installed. Therefore the import command skipped the installation. +--- +title: import Command +description: imports the list of installed applications. +ms.date: 05/02/2021 +ms.topic: overview +ms.localizationpriority: medium +--- + +# import command (winget) + +The **import** command of the [winget](index.md) tool imports a JSON file of apps to install. The **import** command combined with the [**export**](export.md) command allows you to batch install applications on your PC. + +The **import** command is often used to share your developer environment or build up your PC image with your favorite apps. + +## Usage + +`winget import [-i] []` + +![import](images/import.png) + +## Arguments + +The following arguments are available. +| Argument | Description | +|-------------|-------------| +| **-i, --import-file** | JSON file describing the packages to install + +## Options + +The options allow you to customize the import experience to meet your needs. + +| Option | Description | +|-------------|-------------| +| **--ignore-unavailable** | Suppresses errors if the app requested is unavailable | +| **--ignore-versions** | Ignores versions specified in the JSON file and installs the latest available version | +| **--no-upgrade** | Skips upgrade if an installed version already exists | +| **--accept-package-agreements** | Accept all license agreements for packages | +| **--accept-source-agreements** | Accept all source agreements during source operations | +| **-?, --help** | Shows help about the selected command | +| **--wait** | Prompts the user to press any key before exiting | +| **--logs, --open-logs** | Open the default logs location | +| **--verbose, --verbose-logs** | Enables verbose logging for winget | +| **--disable-interactivity** | Disable interactive prompts | + +## JSON Schema +The driving force behind the **import** command is the JSON file. You can see the [schema for the JSON file](https://aka.ms/winget-packages.schema.1.0.json). + +The JSON file includes the following hierarchy: +| Entry | Description | +|-------------|-------------| +| **Sources** | The sources application manifests come from. | +| **Packages** | The collection of packages to install. | +| **PackageIdentifier** | The Windows Package Manager package identifier used to specify the package. | +| **Version** | [optional] The specific version of the package to install. | + +## Importing files + +When the Windows Package Manager imports the JSON file, it attempts to install the specified applications in a serial fashion. If the application is not available or the application is already installed, it will notify the user of that case. + +![import](images/import-command.png) + +You will notice in the example above, **Microsoft.VisualStudioCode** and **JanDeDobbeleer.OhMyPosh** were already installed. Therefore the import command skipped the installation. diff --git a/doc/windows/package-manager/winget/settings.md b/doc/windows/package-manager/winget/settings.md index 27ff660acd..a4fc50e16d 100644 --- a/doc/windows/package-manager/winget/settings.md +++ b/doc/windows/package-manager/winget/settings.md @@ -19,7 +19,7 @@ Launch your default JSON editing tool: `winget settings` ![Screenshot of the Windows Package Manager Settings.](images/settings.png) -When you launch the settings for the first time, there will be no settings specified. At the top of the JSON we provide a [link](https://aka.ms/winget-settings) where you can discover the latest experimental features and settings. +When you launch the settings for the first time, there will be no settings specified. At the top of the JSON we provide a link to [https://aka.ms/winget-settings](https://aka.ms/winget-settings) where you can discover the latest experimental features and settings. We have also defined a schema for the settings file. This allows you to use TAB to discover settings and syntax if your JSON editor supports JSON schemas. diff --git a/doc/windows/package-manager/winget/validate.md b/doc/windows/package-manager/winget/validate.md index 31a7d93eb6..b062ebf05a 100644 --- a/doc/windows/package-manager/winget/validate.md +++ b/doc/windows/package-manager/winget/validate.md @@ -8,7 +8,7 @@ ms.localizationpriority: medium # validate command (winget) -The **validate** command of the [winget](index.md) tool validates a [manifest](../package/manifest.md) for submitting software to the **Microsoft Community Package Manifest Repository** on GitHub. The manifest must be a YAML file that follows the [specification](/doc/ManifestSpecv1.0.md). +The **validate** command of the [winget](index.md) tool validates a [manifest](../package/manifest.md) for submitting software to the **Microsoft Community Package Manifest Repository** on GitHub. The manifest must be a YAML file that follows the [specification](https://github.com/microsoft/winget-pkgs/blob/master/doc/manifest/README.md). ## Usage diff --git a/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json b/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json index 98da3b2c34..62c878d96a 100644 --- a/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json +++ b/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json @@ -601,6 +601,42 @@ "type": [ "boolean", "null" ], "description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages." }, + "Authentication": { + "type": [ "object", "null" ], + "properties": { + "AuthenticationType": { + "type": "string", + "enum": [ + "none", + "microsoftEntraId", + "microsoftEntraIdForAzureBlobStorage" + ], + "description": "The authentication type" + }, + "MicrosoftEntraIdAuthenticationInfo": { + "type": [ "object", "null" ], + "properties": { + "Resource": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The resource value for Microsoft Entra Id authentication." + }, + "Scope": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The scope value for Microsoft Entra Id authentication." + } + }, + "description": "The Microsoft Entra Id authentication info" + } + }, + "required": [ + "AuthenticationType" + ], + "description": "The authentication requirement for downloading the installer." + }, "Installer": { "type": "object", "properties": { @@ -724,6 +760,9 @@ }, "ArchiveBinariesDependOnPath": { "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" } }, "required": [ @@ -846,6 +885,9 @@ "ArchiveBinariesDependOnPath": { "$ref": "#/definitions/ArchiveBinariesDependOnPath" }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, "Installers": { "type": "array", "items": { diff --git a/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json b/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json index dd48208c79..9f7c09cbb3 100644 --- a/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json +++ b/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json @@ -702,6 +702,42 @@ "type": [ "boolean", "null" ], "description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages." }, + "Authentication": { + "type": [ "object", "null" ], + "properties": { + "AuthenticationType": { + "type": "string", + "enum": [ + "none", + "microsoftEntraId", + "microsoftEntraIdForAzureBlobStorage" + ], + "description": "The authentication type" + }, + "MicrosoftEntraIdAuthenticationInfo": { + "type": [ "object", "null" ], + "properties": { + "Resource": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The resource value for Microsoft Entra Id authentication." + }, + "Scope": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The scope value for Microsoft Entra Id authentication." + } + }, + "description": "The Microsoft Entra Id authentication info" + } + }, + "required": [ + "AuthenticationType" + ], + "description": "The authentication requirement for downloading the installer." + }, "Installer": { "type": "object", "properties": { @@ -825,6 +861,9 @@ }, "ArchiveBinariesDependOnPath": { "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" } }, "required": [ @@ -1070,6 +1109,9 @@ "ArchiveBinariesDependOnPath": { "$ref": "#/definitions/ArchiveBinariesDependOnPath" }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, "Installers": { "type": "array", "items": { diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json index 099ebe8080..3aaf870bca 100644 --- a/schemas/JSON/settings/settings.schema.0.2.json +++ b/schemas/JSON/settings/settings.schema.0.2.json @@ -27,7 +27,7 @@ "accent", "rainbow", "retro", - "sixel", + "sixel", "disabled" ] }, @@ -204,8 +204,9 @@ "properties": { "defaultModuleRoot": { "description": "The default root directory where PowerShell modules are installed to when applying a configuration.", - "type": "string", - "maxLength": 32767 + "type": "string", + "maxLength": 32767, + "default": "%LOCALAPPDATA%/Microsoft/WinGet/Configuration/Modules/" } } }, @@ -301,13 +302,19 @@ }, "fonts": { "description": "Enable support for managing fonts", - "type": "boolean", - "default": false + "type": "boolean", + "default": false } } } }, "allOf": [ + { + "properties": { + "source": { "$ref": "#/definitions/Source" } + }, + "additionalItems": true + }, { "properties": { "visual": { "$ref": "#/definitions/Visual" } @@ -322,7 +329,7 @@ }, { "properties": { - "source": { "$ref": "#/definitions/Source" } + "installPrefReq": { "$ref": "#/definitions/InstallPrefReq" } }, "additionalItems": true }, @@ -332,6 +339,24 @@ }, "additionalItems": true }, + { + "properties": { + "uninstallBehavior": { "$ref": "#/definitions/UninstallBehavior" } + }, + "additionalItems": true + }, + { + "properties": { + "configureBehavior": { "$ref": "#/definitions/ConfigureBehavior" } + }, + "additionalItems": true + }, + { + "properties": { + "downloadBehavior": { "$ref": "#/definitions/DownloadBehavior" } + }, + "additionalItems": true + }, { "properties": { "telemetry": { "$ref": "#/definitions/Telemetry" } diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 45c9c0fa04..f78059aafb 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -549,7 +549,7 @@ $(SolutionDir)\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Processor\Microsoft.Management.Configuration.Processor.winmd - $(SolutionDir)\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Processor\net6.0-windows10.0.22000.0\win\Microsoft.Management.Configuration.Processor.winmd + $(SolutionDir)\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Processor\net8.0-windows10.0.22000.0\win\Microsoft.Management.Configuration.Processor.winmd diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 0b1864df3c..f8490ec1ef 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -277,9 +277,9 @@ namespace AppInstaller::CLI // Authentication arguments case Execution::Args::Type::AuthenticationMode: - return { type, "authentication-mode"_liv }; + return { type, "authentication-mode"_liv, ArgTypeCategory::CopyValueToSubContext }; case Execution::Args::Type::AuthenticationAccount: - return { type, "authentication-account"_liv }; + return { type, "authentication-account"_liv, ArgTypeCategory::CopyValueToSubContext }; // Used for demonstration purposes case Execution::Args::Type::ExperimentalArg: diff --git a/src/AppInstallerCLICore/Commands/COMCommand.cpp b/src/AppInstallerCLICore/Commands/COMCommand.cpp index 4af70ddc13..d50c47869d 100644 --- a/src/AppInstallerCLICore/Commands/COMCommand.cpp +++ b/src/AppInstallerCLICore/Commands/COMCommand.cpp @@ -20,7 +20,8 @@ namespace AppInstaller::CLI // IMPORTANT: To use this command, the caller should have already retrieved the package manifest (GetManifest()) and added it to the Context Data void COMDownloadCommand::ExecuteInternal(Context& context) const { - context << + context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << @@ -51,6 +52,7 @@ namespace AppInstaller::CLI void COMRepairCommand::ExecuteInternal(Execution::Context& context) const { context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << Workflow::SelectApplicableInstallerIfNecessary << Workflow::RepairSinglePackage; } diff --git a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp index bf42b837be..e06254427a 100644 --- a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp @@ -78,7 +78,9 @@ namespace AppInstaller::CLI void DownloadCommand::ExecuteInternal(Context& context) const { - context.SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); + context.SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); + + context << Workflow::InitializeInstallerDownloadAuthenticatorsMap; if (context.Args.Contains(Execution::Args::Type::Manifest)) { diff --git a/src/AppInstallerCLICore/Commands/ImportCommand.cpp b/src/AppInstallerCLICore/Commands/ImportCommand.cpp index 744599c1d5..fefc0a6679 100644 --- a/src/AppInstallerCLICore/Commands/ImportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ImportCommand.cpp @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "ImportCommand.h" +#include "ImportCommand.h" +#include "Workflows/DownloadFlow.h" #include "Workflows/CompletionFlow.h" #include "Workflows/ImportExportFlow.h" #include "Workflows/MultiQueryFlow.h" @@ -41,7 +42,8 @@ namespace AppInstaller::CLI void ImportCommand::ExecuteInternal(Execution::Context& context) const { - context << + context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) << Workflow::VerifyFile(Execution::Args::Type::ImportFile) << Workflow::ReadImportFile << diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 097719e70c..c93bf59a80 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -5,6 +5,7 @@ #include "CheckpointManager.h" #include "InstallCommand.h" #include "Workflows/CompletionFlow.h" +#include "Workflows/DownloadFlow.h" #include "Workflows/InstallFlow.h" #include "Workflows/UpdateFlow.h" #include "Workflows/MultiQueryFlow.h" @@ -118,6 +119,8 @@ namespace AppInstaller::CLI { context.SetFlags(ContextFlag::ShowSearchResultsOnPartialFailure); + context << Workflow::InitializeInstallerDownloadAuthenticatorsMap; + if (context.Args.Contains(Execution::Args::Type::Manifest)) { context << diff --git a/src/AppInstallerCLICore/Commands/RepairCommand.cpp b/src/AppInstallerCLICore/Commands/RepairCommand.cpp index 39543f527d..3df1a6a0e6 100644 --- a/src/AppInstallerCLICore/Commands/RepairCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RepairCommand.cpp @@ -4,6 +4,7 @@ #include "RepairCommand.h" #include "Workflows/RepairFlow.h" #include "Workflows/CompletionFlow.h" +#include "Workflows/DownloadFlow.h" #include "Workflows/InstallFlow.h" namespace AppInstaller::CLI @@ -94,6 +95,7 @@ namespace AppInstaller::CLI context.SetFlags(Execution::ContextFlag::InstallerExecutionUseRepair); context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::OpenSource() << Workflow::OpenCompositeSource(DetermineInstalledSource(context)); diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index 5580efa14a..c2264129e9 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -154,6 +154,7 @@ namespace AppInstaller::CLI keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableRoot }, Runtime::GetPathTo(Runtime::PathName::PortablePackageMachineRoot, true).u8string() }); keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableRoot86 }, Runtime::GetPathTo(Runtime::PathName::PortablePackageMachineRootX86, true).u8string() }); keyDirectories.OutputLine({ Resource::LocString{ Resource::String::InstallerDownloads }, Runtime::GetPathTo(Runtime::PathName::UserProfileDownloads, true).u8string() }); + keyDirectories.OutputLine({ Resource::LocString{ Resource::String::ConfigurationModules }, Runtime::GetPathTo(Runtime::PathName::ConfigurationModules, true).u8string() }); keyDirectories.Complete(); context.Reporter.Info() << std::endl; } diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index a5c2701ae2..e3dedc94e4 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "UpgradeCommand.h" #include "Workflows/CompletionFlow.h" +#include "Workflows/DownloadFlow.h" #include "Workflows/InstallFlow.h" #include "Workflows/MultiQueryFlow.h" #include "Workflows/UpdateFlow.h" @@ -157,6 +158,7 @@ namespace AppInstaller::CLI } context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::OpenSource() << Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context)); diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index f75193f9ec..1acc86c707 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -172,6 +172,10 @@ namespace AppInstaller::CLI AICLI_LOG(CLI, Error, << "Operation blocked by Group Policy: " << policy.RegValueName()); context.Reporter.Error() << Resource::String::DisabledByGroupPolicy(policy.PolicyName()) << std::endl; return APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; + } + catch (...) + { + return Workflow::HandleException(context, std::current_exception()); } return Execute(context, command); diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index 97d82f63ca..49789bb11d 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -323,6 +323,7 @@ namespace AppInstaller::CLI::Execution clone->EnableSignalTerminationHandler(); } CopyArgsToSubContext(clone.get()); + CopyDataToSubContext(clone.get()); return clone; } @@ -342,6 +343,17 @@ namespace AppInstaller::CLI::Execution } } + void Context::CopyDataToSubContext(Context* subContext) + { +#define COPY_DATA_IF_EXISTS(dataType) \ + if (this->Contains(dataType)) \ + { \ + subContext->Add(this->Get()); \ + } + + COPY_DATA_IF_EXISTS(Data::InstallerDownloadAuthenticators); + } + void Context::EnableSignalTerminationHandler(bool enabled) { SetSignalTerminationHandlerContext(enabled, this); diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index a5fa557570..5223fdd972 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -192,6 +192,9 @@ namespace AppInstaller::CLI::Execution // Copies the args that are also needed in a sub-context. E.g., silent void CopyArgsToSubContext(Context* subContext); + // Copies the execution data that are also needed in a sub-context. E.g., shared installer download authenticator map + void CopyDataToSubContext(Context* subContext); + // Neither virtual functions nor member fields can be inside AICLI_DISABLE_TEST_HOOKS // or we could have ODR violations that lead to nasty bugs. So we will simply never // use this if AICLI_DISABLE_TEST_HOOKS is defined. diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index 4633b05d33..1796ff5000 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "CompletionData.h" @@ -67,6 +68,7 @@ namespace AppInstaller::CLI::Execution ModifyPath, RepairString, MsixDigests, + InstallerDownloadAuthenticators, Max }; @@ -290,5 +292,12 @@ namespace AppInstaller::CLI::Execution // The pair is { URL, Digest } using value_t = std::vector>; }; + + template<> + struct DataMapping + { + // The authenticator map shared with sub contexts + using value_t = std::shared_ptr>; + }; } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 0765d9f0d8..aa37c797b1 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -94,6 +94,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleNameOnly); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModulePath); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModulePathArgError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModules); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleWithDetails); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotEnabledMessage); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNoTestRun); @@ -301,8 +302,11 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstalledScopeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal); WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationFailed); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadCommandProhibited); WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloaded); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadRequiresAuthentication); WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloads); WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck); diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 084cbb4d3f..b933e50730 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -23,6 +23,12 @@ namespace AppInstaller::CLI::Workflow namespace { + constexpr std::string_view s_MicrosoftEntraIdAuthorizationHeader = "Authorization"sv; + // By default Azure blob storage does not accept Microsoft Entra Id authentication. + // https://learn.microsoft.com/en-us/rest/api/storageservices/versioning-for-the-azure-storage-services#authorize-requests-by-using-microsoft-entra-id-shared-key-or-shared-key-lite + constexpr std::string_view s_AzureBlobStorageApiVersionHeader = "x-ms-version"sv; + constexpr std::string_view s_AzureBlobStorageApiVersionValue = "2020-04-08"sv; + // Get the base download directory path for the installer. // Also creates the directory as necessary. std::filesystem::path GetInstallerBaseDownloadPath(Execution::Context& context) @@ -191,6 +197,66 @@ namespace AppInstaller::CLI::Workflow return false; } + + std::string GetInstallerDownloadAuthenticationToken(const AppInstaller::Authentication::AuthenticationInfo& authInfo, Execution::Context& context) + { + // First check if authenticator is already created + auto& authenticatorsMap = context.Get(); + auto authenticatorItr = authenticatorsMap->find(authInfo); + if (authenticatorItr == authenticatorsMap->end()) + { + AppInstaller::Authentication::Authenticator authenticator{ authInfo, GetAuthenticationArguments(context) }; + authenticatorsMap->emplace(authInfo, std::move(authenticator)); + } + + // Get the authenticator for auth. + authenticatorItr = authenticatorsMap->find(authInfo); + THROW_HR_IF(E_UNEXPECTED, authenticatorItr == authenticatorsMap->end()); + + auto authResult = authenticatorItr->second.AuthenticateForToken(); + if (FAILED(authResult.Status)) + { + AICLI_LOG(Repo, Error, << "Authentication failed for installer download. Result: " << authResult.Status); + THROW_HR_MSG(authResult.Status, "Failed to authenticate for installer download."); + } + + return authResult.Token; + } + + // Get additional headers for installer download request. Auth headers are acquired here. + std::vector GetInstallerDownloadAuthenticationHeaders(const AppInstaller::Manifest::ManifestInstaller& installer, Execution::Context& context) + { + std::vector result; + + switch (installer.AuthInfo.Type) + { + case AppInstaller::Authentication::AuthenticationType::None: + // No auth needed + break; + case AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId: + case AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage: + context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::InstallerDownloadRequiresAuthentication << std::endl; + result.push_back({ std::string{ s_MicrosoftEntraIdAuthorizationHeader }, Authentication::CreateBearerToken(GetInstallerDownloadAuthenticationToken(installer.AuthInfo, context)), true }); + if (installer.AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage) + { + result.push_back({ std::string{ s_AzureBlobStorageApiVersionHeader }, std::string{ s_AzureBlobStorageApiVersionValue }, false }); + } + break; + case AppInstaller::Authentication::AuthenticationType::Unknown: + default: + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED, "The package installer requires authentication that is not supported."); + } + + // Log result before return + std::string logMessage = "Installer download headers: "; + for (const auto& header : result) + { + logMessage += header.Name + ": " + (header.IsAuth ? "" : header.Value) + "; "; + } + AICLI_LOG(CLI, Info, << logMessage); + + return result; + } } void DownloadInstaller(Execution::Context& context) @@ -324,6 +390,26 @@ namespace AppInstaller::CLI::Workflow // Use the SHA256 hash of the installer as the identifier for the download downloadInfo.ContentId = SHA256::ConvertToString(installer.Sha256); + try + { + downloadInfo.RequestHeaders = GetInstallerDownloadAuthenticationHeaders(installer, context); + } + catch (const wil::ResultException& re) + { + AICLI_LOG(CLI, Error, << "Authentication failed for installer download. Error code: " << re.GetErrorCode()); + + if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED) + { + context.Reporter.Error() << Resource::String::InstallerDownloadAuthenticationNotSupported << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::InstallerDownloadAuthenticationFailed << std::endl; + } + + AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); + } + context.Reporter.Info() << Resource::String::Downloading << ' ' << Execution::UrlEmphasis << installer.Url << std::endl; DownloadResult downloadResult; @@ -686,4 +772,9 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); } } + + void InitializeInstallerDownloadAuthenticatorsMap(Execution::Context& context) + { + context.Add(std::make_shared>()); + } } diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.h b/src/AppInstallerCLICore/Workflows/DownloadFlow.h index ab7b448772..8a3b4ab8d4 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.h +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.h @@ -85,4 +85,11 @@ namespace AppInstaller::CLI::Workflow // Inputs: Installer // Outputs: None void EnsureSupportForDownload(Execution::Context& context); + + // This method initializes an empty InstallerDownloadAuthenticators map. + // InstallerDownloadAuthenticators map is for reusing authenticators when downloading multiple installers. + // Required Args: None + // Inputs: None + // Outputs: New empty InstallerDownloadAuthenticators + void InitializeInstallerDownloadAuthenticatorsMap(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp index 6ad4326013..ddb863a1b7 100644 --- a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp @@ -40,7 +40,6 @@ namespace AppInstaller::CLI::Workflow auto& source = context.Get(); for (const auto& query : *context.Args.GetArgs(Execution::Args::Type::MultiQuery)) { - auto searchContextPtr = context.CreateSubContext(); Execution::Context& searchContext = *searchContextPtr; auto previousThreadGlobals = searchContext.SetForCurrentThread(); @@ -51,7 +50,6 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << "Creating search query for package [" << query << "]"); searchContext << GetSearchRequestForSingle; - packageSubContexts.emplace_back(std::move(searchContextPtr)); } @@ -148,5 +146,4 @@ namespace AppInstaller::CLI::Workflow context.Add(std::move(packageSubContexts)); } - -} \ No newline at end of file +} diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj index 179ffb1583..6c224bf06f 100644 --- a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj +++ b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -1,7 +1,7 @@ - net6.0-windows + net8.0-windows $(SolutionDir)$(Platform)\$(Configuration)\AppInstallerCLIE2ETests\ false x64;x86 @@ -11,7 +11,7 @@ - 10.0.22000.52 + 10.0.22000.53 @@ -25,7 +25,7 @@ - + @@ -38,11 +38,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - diff --git a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj index 06a24b4e09..a9e40fc31e 100644 --- a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj +++ b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj @@ -217,17 +217,14 @@ copy "$(TargetDir)\resources.pri" "$(TargetDir)\AppInstallerCLI\resources.pri" - - win10-arm - - win10-arm64 + win-arm64 - win10-x64 + win-x64 - win10-x86 + win-x86 @@ -253,7 +250,7 @@ ConfigurationRemotingServer\Microsoft.Management.Configuration.Projection.dll - + ConfigurationRemotingServer true diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 7897bb985a..2f870d9078 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1812,7 +1812,7 @@ Please specify one of them using the --source option to proceed. Configuration successfully applied. - + Unit successfully applied. @@ -2762,7 +2762,7 @@ Please specify one of them using the --source option to proceed. Specify the account to be used for authentication - Failed to add source. This winget version does not support the source's authentication method. Try upgrade to latest winget version. + Failed to add source. This winget version does not support the source's authentication method. Try upgrading to latest winget version. {Locked="winget"} @@ -3147,7 +3147,7 @@ Please specify one of them using the --source option to proceed. Downloaded zero byte installer; ensure that your network connection is working properly. - + Manage fonts @@ -3183,4 +3183,18 @@ Please specify one of them using the --source option to proceed. Version + + Configuration Modules + PowerShell Modules that are used for the Configuration feature + + + The package installer requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the installer download url. + + + Failed to download installer. This winget version does not support the installer download authentication method. Try upgrading to latest winget version. + {Locked="winget"} + + + Failed to download installer. Authentication failed. + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 367e3108a2..5bf26983b7 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -817,6 +817,9 @@ true + + true + true @@ -1073,4 +1076,4 @@ - \ No newline at end of file + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 50ed22705d..5895890272 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -825,6 +825,9 @@ TestData + + TestData + TestData\MultiFileManifestV1 diff --git a/src/AppInstallerCLITests/CustomHeader.cpp b/src/AppInstallerCLITests/CustomHeader.cpp index 1651a71647..e299523251 100644 --- a/src/AppInstallerCLITests/CustomHeader.cpp +++ b/src/AppInstallerCLITests/CustomHeader.cpp @@ -41,6 +41,14 @@ namespace })delimiter"); } +// In RestClient.cpp tests +extern RestClient CreateRestClient( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const Http::HttpClientHelper& helper, + const Authentication::AuthenticationArguments& authArgs = {}); + TEST_CASE("RestClient_CustomHeader", "[RestSource][CustomHeader]") { utility::string_t sample = _XPLATSTR( @@ -55,7 +63,7 @@ TEST_CASE("RestClient_CustomHeader", "[RestSource][CustomHeader]") std::optional customHeader = "Testing custom header"; auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader.value())); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, std::move(helper), {}); + RestClient client = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -104,7 +112,7 @@ TEST_CASE("RestSourceSearch_CustomHeaderExceedingSize", "[RestSource][CustomHead auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader)); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - REQUIRE_THROWS_HR(RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, std::move(helper), {}), + REQUIRE_THROWS_HR(CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper), APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH); } @@ -122,7 +130,7 @@ TEST_CASE("RestClient_CustomUserAgentHeader", "[RestSource][CustomHeader]") std::string testCaller = "TestCaller"; auto header = std::make_pair<>(web::http::header_names::user_agent, JSON::GetUtilityString(Runtime::GetUserAgent(testCaller))); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, std::move(helper), {}); + RestClient client = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -139,6 +147,6 @@ TEST_CASE("RestClient_DefaultUserAgentHeader", "[RestSource][CustomHeader]") auto header = std::make_pair<>(web::http::header_names::user_agent, JSON::GetUtilityString(Runtime::GetDefaultUserAgent())); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, std::move(helper), {}); + RestClient client = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } diff --git a/src/AppInstallerCLITests/DownloadFlow.cpp b/src/AppInstallerCLITests/DownloadFlow.cpp index 3e76830a33..c4e1a42f21 100644 --- a/src/AppInstallerCLITests/DownloadFlow.cpp +++ b/src/AppInstallerCLITests/DownloadFlow.cpp @@ -1,10 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include "TestHooks.h" +#include "AppInstallerRuntime.h" #include "WorkflowCommon.h" #include using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Authentication; using namespace AppInstaller::CLI; TEST_CASE("DownloadFlow_DownloadCommandProhibited", "[DownloadFlow][workflow]") @@ -20,5 +24,118 @@ TEST_CASE("DownloadFlow_DownloadCommandProhibited", "[DownloadFlow][workflow]") // Verify AppInfo is printed REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); - REQUIRE(downloadOutput.str().find(Resource::LocString(Resource::String::InstallerDownloadCommandProhibited).get()) != std::string::npos); + REQUIRE(downloadOutput.str().find(CLI::Resource::LocString(CLI::Resource::String::InstallerDownloadCommandProhibited).get()) != std::string::npos); +} + +AppInstaller::Utility::DownloadResult ValidateAzureBlobStorageAuthHeaders( + const std::string&, + const std::filesystem::path& dest, + AppInstaller::Utility::DownloadType, + AppInstaller::IProgressCallback&, + std::optional info) +{ + REQUIRE(info); + REQUIRE(info->RequestHeaders.size() > 0); + REQUIRE(info->RequestHeaders[0].IsAuth); + REQUIRE(info->RequestHeaders[0].Name == "Authorization"); + REQUIRE(info->RequestHeaders[0].Value == "Bearer TestToken"); + REQUIRE_FALSE(info->RequestHeaders[1].IsAuth); + REQUIRE(info->RequestHeaders[1].Name == "x-ms-version"); + // Not validating x-ms-version value + + std::ofstream file(dest, std::ofstream::out); + file << "test"; + file.close(); + + AppInstaller::Utility::DownloadResult result; + result.Sha256Hash = AppInstaller::Utility::SHA256::ConvertToBytes("65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B"); + return result; +} + +TEST_CASE("DownloadFlow_DownloadWithInstallerAuthenticationSuccess", "[DownloadFlow][workflow]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + // Set authentication success result override + std::string expectedToken = "TestToken"; + AuthenticationResult authResultOverride; + authResultOverride.Status = S_OK; + authResultOverride.Token = expectedToken; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // Set auth header validation override + TestHook::SetDownloadResult_Function_Override downloadFunctionOverride({ &ValidateAzureBlobStorageAuthHeaders }); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("ManifestV1_10-InstallerAuthentication.yaml").GetPath().u8string()); + TestCommon::TempDirectory tempDirectory("TempDownload"); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory.GetPath().u8string()); + + DownloadCommand download({}); + download.Execute(context); + INFO(downloadOutput.str()); + + // Verify success + REQUIRE_FALSE(context.IsTerminated()); + REQUIRE(context.GetTerminationHR() == S_OK); +} + +TEST_CASE("DownloadFlow_DownloadWithInstallerAuthenticationNotSupported", "[DownloadFlow][workflow]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + // Set authentication failed result + AuthenticationResult authResultOverride; + authResultOverride.Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("ManifestV1_10-InstallerAuthentication.yaml").GetPath().u8string()); + + DownloadCommand download({}); + download.Execute(context); + INFO(downloadOutput.str()); + + // Verify AppInfo is printed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + REQUIRE(downloadOutput.str().find(CLI::Resource::LocString(CLI::Resource::String::InstallerDownloadAuthenticationNotSupported).get()) != std::string::npos); +} + +TEST_CASE("DownloadFlow_DownloadWithInstallerAuthenticationFailed", "[DownloadFlow][workflow]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + // Set authentication failed result + AuthenticationResult authResultOverride; + authResultOverride.Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("ManifestV1_10-InstallerAuthentication.yaml").GetPath().u8string()); + + DownloadCommand download({}); + download.Execute(context); + INFO(downloadOutput.str()); + + // Verify AppInfo is printed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED); + REQUIRE(downloadOutput.str().find(CLI::Resource::LocString(CLI::Resource::String::InstallerDownloadAuthenticationFailed).get()) != std::string::npos); } diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp index a864454f76..7713d791d5 100644 --- a/src/AppInstallerCLITests/RestClient.cpp +++ b/src/AppInstallerCLITests/RestClient.cpp @@ -17,6 +17,16 @@ using namespace AppInstaller::Repository::Rest::Schema; const std::string TestRestUri = "http://restsource.net"; +RestClient CreateRestClient( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const Http::HttpClientHelper& helper, + const Authentication::AuthenticationArguments& authArgs = {}) +{ + return RestClient::Create(restApi, customHeader, caller, helper, RestClient::GetInformation(restApi, customHeader, caller, helper), authArgs); +} + TEST_CASE("GetLatestCommonVersion", "[RestSource]") { std::set wingetSupportedContracts = { Version {"1.0.0"}, Version {"1.2.0"} }; @@ -103,7 +113,7 @@ TEST_CASE("GetInformation_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper)); + IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); REQUIRE(information.SourceIdentifier == "Source123"); REQUIRE(information.ServerSupportedVersions.size() == 2); REQUIRE(information.ServerSupportedVersions.at(0) == "1.0.0"); @@ -165,7 +175,7 @@ TEST_CASE("GetInformation_WithAuthenticationInfo_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper)); + IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); REQUIRE(information.SourceIdentifier == "Source123"); REQUIRE(information.ServerSupportedVersions.size() == 1); REQUIRE(information.ServerSupportedVersions.at(0) == "1.7.0"); @@ -208,7 +218,7 @@ TEST_CASE("GetInformation_Fail_AgreementsWithoutIdentifier", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); } TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") @@ -226,7 +236,7 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") }})delimiter"); HttpClientHelper helper1{ GetTestRestRequestHandler(web::http::status_codes::OK, sample1) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper1)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper1), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); utility::string_t sample2 = _XPLATSTR( R"delimiter({ @@ -245,7 +255,7 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") }})delimiter"); HttpClientHelper helper2{ GetTestRestRequestHandler(web::http::status_codes::OK, sample2) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper2)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper2), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); utility::string_t sample3 = _XPLATSTR( R"delimiter({ @@ -263,7 +273,25 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") }})delimiter"); HttpClientHelper helper3{ GetTestRestRequestHandler(web::http::status_codes::OK, sample3) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper3)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper3), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + + utility::string_t sample4 = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraIdForAzureBlobStorage" + } + }})delimiter"); + + HttpClientHelper helper4{ GetTestRestRequestHandler(web::http::status_codes::OK, sample4) }; + Authentication::AuthenticationArguments authArgs; + authArgs.Mode = Authentication::AuthenticationMode::Silent; + Version version_1_7{ "1.7.0" }; + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, RestClient::GetInformation(TestRestUri, {}, {}, helper4), authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); } TEST_CASE("RestClientCreate_UnsupportedVersion", "[RestSource]") @@ -278,7 +306,7 @@ TEST_CASE("RestClientCreate_UnsupportedVersion", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); } TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") @@ -298,7 +326,7 @@ TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper), std::move(authArgs)), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); } TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") @@ -321,7 +349,7 @@ TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Unknown; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper), std::move(authArgs)), E_UNEXPECTED); + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), E_UNEXPECTED); } TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") @@ -336,7 +364,7 @@ TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper), {}); + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -373,7 +401,7 @@ TEST_CASE("RestClientCreate_1.1_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper)); + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); auto information = client.GetSourceInformation(); REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); @@ -439,7 +467,7 @@ TEST_CASE("RestClientCreate_1.7_Success", "[RestSource]") Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper), std::move(authArgs)); + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper, authArgs); REQUIRE(client.GetSourceIdentifier() == "Source123"); auto information = client.GetSourceInformation(); REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml new file mode 100644 index 0000000000..ac3ea15d98 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml @@ -0,0 +1,22 @@ +PackageIdentifier: AppInstallerCliTest.InstallerAuthentication +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer Authentication +Publisher: Microsoft Corporation +AppMoniker: AICLITestInstallerAuthentication +License: Test +Authentication: + AuthenticationType: microsoftEntraId + MicrosoftEntraIdAuthenticationInfo: + Resource: TestResource + Scope: TestScope + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Authentication: + AuthenticationType: microsoftEntraIdForAzureBlobStorage +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestHooks.h b/src/AppInstallerCLITests/TestHooks.h index 3917c8d1c2..3d862c8270 100644 --- a/src/AppInstallerCLITests/TestHooks.h +++ b/src/AppInstallerCLITests/TestHooks.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -93,6 +94,16 @@ namespace AppInstaller void SetLicensingHttpPipelineStage_Override(std::shared_ptr value); } + + namespace Utility::TestHooks + { + void SetDownloadResult_Function_Override(std::function info)>* value); + } } namespace TestHook @@ -301,4 +312,30 @@ namespace TestHook AppInstaller::MSStore::TestHooks::SetLicensingHttpPipelineStage_Override(nullptr); } }; + + struct SetDownloadResult_Function_Override + { + SetDownloadResult_Function_Override(std::function info)> value) : m_downloadFunction(std::move(value)) + { + AppInstaller::Utility::TestHooks::SetDownloadResult_Function_Override(&m_downloadFunction); + } + + ~SetDownloadResult_Function_Override() + { + AppInstaller::Utility::TestHooks::SetDownloadResult_Function_Override(nullptr); + } + + private: + std::function info)> m_downloadFunction; + }; } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 65144a8d43..0bbe8243cc 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -1417,6 +1417,49 @@ TEST_CASE("WriteV1_10SingletonManifestAndVerifyContents", "[ManifestCreation]") VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_10 }, true); } +// Since Authentication is not supported in community repo and will cause manifest validation failure, +// we are not adding Authentication in v1_10 manifests. Instead a separate test is created for Authentication. +TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[ManifestValidation]") +{ + // Read manifest + TempDirectory testDirectory{ "TestManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_10-InstallerAuthentication.yaml" }, testDirectory); + Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + + // Verify content + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_10 }); + REQUIRE(testManifest.DefaultInstallerInfo.AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId); + REQUIRE(testManifest.DefaultInstallerInfo.AuthInfo.MicrosoftEntraIdInfo); + REQUIRE(testManifest.DefaultInstallerInfo.AuthInfo.MicrosoftEntraIdInfo->Resource == "TestResource"); + REQUIRE(testManifest.DefaultInstallerInfo.AuthInfo.MicrosoftEntraIdInfo->Scope == "TestScope"); + REQUIRE(testManifest.Installers.size() == 1); + REQUIRE(testManifest.Installers[0].AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage); + REQUIRE(testManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo); + REQUIRE(testManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo->Resource == "https://storage.azure.com/"); + REQUIRE(testManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo->Scope.empty()); + + // Manifest Validation. Only error is "Authentication not supported". + auto errors = ValidateManifest(testManifest, true); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0].GetErrorMessage() == "Field is not supported."); + REQUIRE(errors[0].Context == "Authentication"); + + // Write manifest + TempDirectory exportedDirectory{ "ExportedManifest" }; + std::filesystem::path exportedManifestPath = exportedDirectory.GetPath() / "ExportedManifest.yaml"; + YamlWriter::OutputYamlFile(testManifest, testManifest.Installers[0], exportedManifestPath); + + // Read back and validate content + REQUIRE(std::filesystem::exists(exportedManifestPath)); + Manifest exportedManifest = YamlParser::CreateFromPath(exportedDirectory); + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_10 }); + REQUIRE(exportedManifest.Installers.size() == 1); + REQUIRE(exportedManifest.Installers[0].AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage); + REQUIRE(exportedManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo); + REQUIRE(exportedManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo->Resource == "https://storage.azure.com/"); + REQUIRE(exportedManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo->Scope.empty()); +} + TEST_CASE("WriteManifestWithMultipleLocale", "[ManifestCreation]") { Manifest multiLocaleManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp index 448e33d52b..589e35ebc7 100644 --- a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp +++ b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -745,7 +745,7 @@ namespace AppInstaller::Logging AICLI_TraceLoggingStringView(type, "Type"), TraceLoggingUInt32(errorCode, "ErrorCode"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); if (m_useSummary) { diff --git a/src/AppInstallerCommonCore/Architecture.cpp b/src/AppInstallerCommonCore/Architecture.cpp index 6f8ec557f3..9370e0351f 100644 --- a/src/AppInstallerCommonCore/Architecture.cpp +++ b/src/AppInstallerCommonCore/Architecture.cpp @@ -229,22 +229,23 @@ namespace AppInstaller::Utility { Architecture systemArchitecture = Architecture::Unknown; - SYSTEM_INFO systemInfo; - ZeroMemory(&systemInfo, sizeof(SYSTEM_INFO)); - GetNativeSystemInfo(&systemInfo); + USHORT processArchitecture = IMAGE_FILE_MACHINE_UNKNOWN; + USHORT machineArchitecture = IMAGE_FILE_MACHINE_UNKNOWN; + // Just log the error if failed and return architecture Unknown. + LOG_IF_WIN32_BOOL_FALSE(IsWow64Process2(GetCurrentProcess(), &processArchitecture, &machineArchitecture)); - switch (systemInfo.wProcessorArchitecture) + switch (machineArchitecture) { - case PROCESSOR_ARCHITECTURE_AMD64: + case IMAGE_FILE_MACHINE_AMD64: systemArchitecture = Architecture::X64; break; - case PROCESSOR_ARCHITECTURE_ARM: + case IMAGE_FILE_MACHINE_ARM: systemArchitecture = Architecture::Arm; break; - case PROCESSOR_ARCHITECTURE_ARM64: + case IMAGE_FILE_MACHINE_ARM64: systemArchitecture = Architecture::Arm64; break; - case PROCESSOR_ARCHITECTURE_INTEL: + case IMAGE_FILE_MACHINE_I386: systemArchitecture = Architecture::X86; break; } @@ -276,4 +277,4 @@ namespace AppInstaller::Utility return InapplicableArchitecture; } } -} \ No newline at end of file +} diff --git a/src/AppInstallerCommonCore/Authentication/Authentication.cpp b/src/AppInstallerCommonCore/Authentication/Authentication.cpp index ae9c8f121e..3d2e8067a4 100644 --- a/src/AppInstallerCommonCore/Authentication/Authentication.cpp +++ b/src/AppInstallerCommonCore/Authentication/Authentication.cpp @@ -12,7 +12,9 @@ namespace AppInstaller::Authentication { namespace { - const std::string c_BearerTokenPrefix = "Bearer "; + constexpr std::string_view s_BearerTokenPrefix = "Bearer "sv; + // Default Azure Blob Storage resource value. Used when manifest author did not provide specific blob resource. + constexpr std::string_view s_DefaultAzureBlobStorageResource = "https://storage.azure.com/"sv; } Authenticator::Authenticator(AuthenticationInfo info, AuthenticationArguments args) @@ -24,9 +26,9 @@ namespace AppInstaller::Authentication AICLI_LOG(Core, Info, << "AuthenticationArguments values. Mode: " << AuthenticationModeToString(args.Mode) << ", Account: " << args.AuthenticationAccount); - if (info.Type == AuthenticationType::MicrosoftEntraId) + if (info.Type == AuthenticationType::MicrosoftEntraId || info.Type == AuthenticationType::MicrosoftEntraIdForAzureBlobStorage) { - AICLI_LOG(Core, Info, << "Creating WebAccountManagerAuthenticator for MicrosoftEntraId"); + AICLI_LOG(Core, Info, << "Creating WebAccountManagerAuthenticator for " << AuthenticationTypeToString(info.Type)); m_authProvider = std::make_unique(std::move(info), std::move(args)); } } @@ -54,12 +56,48 @@ namespace AppInstaller::Authentication THROW_HR_IF(E_UNEXPECTED, !m_authProvider); return m_authProvider->AuthenticateForToken(); + } + + bool MicrosoftEntraIdAuthenticationInfo::operator<(const MicrosoftEntraIdAuthenticationInfo& other) const + { + // std::tie implements tuple comparison, wherein it checks the first item in the tuple, + // iff the first elements are equal, then the second element is used for comparison, and so on + return std::tie(Resource, Scope) < std::tie(other.Resource, other.Scope); + } + + bool AuthenticationInfo::operator<(const AuthenticationInfo& other) const + { + // std::tie implements tuple comparison, wherein it checks the first item in the tuple, + // iff the first elements are equal, then the second element is used for comparison, and so on + return std::tie(Type, MicrosoftEntraIdInfo) < std::tie(other.Type, other.MicrosoftEntraIdInfo); + } + + void AuthenticationInfo::UpdateRequiredFieldsIfNecessary() + { + // If MicrosoftEntraIdForAzureBlobStorage, populate default resource value if missing. + if (Type == AuthenticationType::MicrosoftEntraIdForAzureBlobStorage) + { + if (MicrosoftEntraIdInfo.has_value()) + { + if (MicrosoftEntraIdInfo->Resource.empty()) + { + MicrosoftEntraIdInfo->Resource = s_DefaultAzureBlobStorageResource; + MicrosoftEntraIdInfo->Scope = ""; + } + } + else + { + MicrosoftEntraIdAuthenticationInfo authInfo; + authInfo.Resource = s_DefaultAzureBlobStorageResource; + MicrosoftEntraIdInfo = std::move(authInfo); + } + } } - bool AuthenticationInfo::ValidateIntegrity() + bool AuthenticationInfo::ValidateIntegrity() const { // For MicrosoftEntraId, Resource is required. - if (Type == AuthenticationType::MicrosoftEntraId) + if (Type == AuthenticationType::MicrosoftEntraId || Type == AuthenticationType::MicrosoftEntraIdForAzureBlobStorage) { return MicrosoftEntraIdInfo.has_value() && !MicrosoftEntraIdInfo->Resource.empty(); } @@ -177,7 +215,9 @@ namespace AppInstaller::Authentication case AuthenticationType::None: return "none"sv; case AuthenticationType::MicrosoftEntraId: - return "microsoftEntraId"sv; + return "microsoftEntraId"sv; + case AuthenticationType::MicrosoftEntraIdForAzureBlobStorage: + return "microsoftEntraIdForAzureBlobStorage"sv; } return "unknown"sv; @@ -195,6 +235,10 @@ namespace AppInstaller::Authentication else if (inStrLower == "microsoftentraid") { result = AuthenticationType::MicrosoftEntraId; + } + else if (inStrLower == "microsoftentraidforazureblobstorage") + { + result = AuthenticationType::MicrosoftEntraIdForAzureBlobStorage; } return result; @@ -238,6 +282,6 @@ namespace AppInstaller::Authentication std::string AppInstaller::Authentication::CreateBearerToken(std::string rawToken) { - return c_BearerTokenPrefix + rawToken; + return std::string{ s_BearerTokenPrefix } + rawToken; } } diff --git a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp index f4bd39befb..bdef661041 100644 --- a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp +++ b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp @@ -31,7 +31,7 @@ namespace AppInstaller::Authentication THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, !m_authInfo.ValidateIntegrity()); THROW_HR_IF(E_UNEXPECTED, m_authArgs.Mode == AuthenticationMode::Unknown); - if (m_authInfo.Type == AuthenticationType::MicrosoftEntraId) + if (IsMicrosoftEntraIdAuthenticationType()) { m_webAccountProvider = WebAuthenticationCoreManager::FindAccountProviderAsync(s_MicrosoftEntraIdProviderId, s_MicrosoftEntraIdAuthority).get(); THROW_HR_IF_MSG(E_UNEXPECTED, !m_webAccountProvider, "Authentication Provider not found for Microsoft Entra Id"); @@ -108,7 +108,7 @@ namespace AppInstaller::Authentication WebAccount result = nullptr; - if (m_authInfo.Type == AuthenticationType::MicrosoftEntraId) + if (IsMicrosoftEntraIdAuthenticationType()) { auto findAccountsResult = WebAuthenticationCoreManager::FindAllAccountsAsync(m_webAccountProvider, s_MicrosoftEntraIdClientId).get(); if (findAccountsResult.Status() == FindAllWebAccountsStatus::Success) @@ -144,7 +144,7 @@ namespace AppInstaller::Authentication { WebTokenRequest request = nullptr; - if (m_authInfo.Type == AuthenticationType::MicrosoftEntraId) + if (IsMicrosoftEntraIdAuthenticationType()) { request = WebTokenRequest { @@ -283,5 +283,10 @@ namespace AppInstaller::Authentication AICLI_LOG(Core, Info, << "HandleGetTokenResult Result: " << result.Status); return result; + } + + bool WebAccountManagerAuthenticator::IsMicrosoftEntraIdAuthenticationType() + { + return m_authInfo.Type == AuthenticationType::MicrosoftEntraId || m_authInfo.Type == AuthenticationType::MicrosoftEntraIdForAzureBlobStorage; } } diff --git a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h index f6374a42a1..76829b5e42 100644 --- a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h +++ b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h @@ -26,5 +26,7 @@ namespace AppInstaller::Authentication AuthenticationResult GetToken(winrt::Windows::Security::Credentials::WebAccount webAccount, bool forceInteractive = false); AuthenticationResult GetTokenSilent(winrt::Windows::Security::Credentials::WebAccount webAccount); AuthenticationResult HandleGetTokenResult(winrt::Windows::Security::Authentication::Web::Core::WebTokenRequestResult requestResult); + + bool IsMicrosoftEntraIdAuthenticationType(); }; -} \ No newline at end of file +} diff --git a/src/AppInstallerCommonCore/DODownloader.cpp b/src/AppInstallerCommonCore/DODownloader.cpp index 988a0f754e..469edfcd3b 100644 --- a/src/AppInstallerCommonCore/DODownloader.cpp +++ b/src/AppInstallerCommonCore/DODownloader.cpp @@ -197,6 +197,21 @@ namespace AppInstaller::Utility SetUnknownProperty(DODownloadProperty_StreamInterface, streamInterface); } + void CustomHeaders(const std::vector& headers) + { + // DODownloadProperty_HttpCustomAuthHeaders is not used (does not work in our auth scenario). It is only used when challenged. + std::string customHeaders; + for (const auto& header : headers) + { + customHeaders += header.Name + ": " + header.Value + "\r\n"; + } + + if (!customHeaders.empty()) + { + SetProperty(DODownloadProperty_HttpCustomHeaders, customHeaders); + } + } + // Properties that may be interesting for future use: // https://docs.microsoft.com/en-us/windows/win32/delivery_optimization/deliveryoptimizationdownloadtypes/ne-deliveryoptimizationdownloadtypes-dodownloadproperty // - DODownloadProperty_CostPolicy :: Allow user to specify how to behave on metered networks @@ -431,6 +446,11 @@ namespace AppInstaller::Utility { download.ContentId(info->ContentId); } + + if (!info->RequestHeaders.empty()) + { + download.CustomHeaders(info->RequestHeaders); + } } download.Start(); diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp index f96e0c0176..13c1be3b8c 100644 --- a/src/AppInstallerCommonCore/Downloader.cpp +++ b/src/AppInstallerCommonCore/Downloader.cpp @@ -102,10 +102,33 @@ namespace AppInstaller::Utility } } +#ifndef AICLI_DISABLE_TEST_HOOKS + namespace TestHooks + { + static std::function info)>* s_Download_Function_Override = nullptr; + + void SetDownloadResult_Function_Override(std::function info)>* value) + { + s_Download_Function_Override = value; + } + } +#endif + DownloadResult WinINetDownloadToStream( const std::string& url, std::ostream& dest, - IProgressCallback& progress) + IProgressCallback& progress, + std::optional info) { // For AICLI_LOG usages with string literals. #pragma warning(push) @@ -139,12 +162,22 @@ namespace AppInstaller::Utility THROW_LAST_ERROR_IF_NULL_MSG(session, "InternetOpen() failed."); + std::string customHeaders; + if (info && info->RequestHeaders.size() > 0) + { + for (const auto& header : info->RequestHeaders) + { + customHeaders += header.Name + ": " + header.Value + "\r\n"; + } + } + std::wstring customHeadersWide = Utility::ConvertToUTF16(customHeaders); + auto urlWide = Utility::ConvertToUTF16(url); wil::unique_hinternet urlFile(InternetOpenUrl( session.get(), urlWide.c_str(), - NULL, - 0, + customHeadersWide.empty() ? NULL : customHeadersWide.c_str(), + customHeadersWide.empty() ? 0 : (DWORD)(customHeadersWide.size()), INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS, // This allows http->https redirection 0)); THROW_LAST_ERROR_IF_NULL_MSG(urlFile, "InternetOpenUrl() failed."); @@ -296,10 +329,10 @@ namespace AppInstaller::Utility std::ostream& dest, DownloadType, IProgressCallback& progress, - std::optional) + std::optional info) { THROW_HR_IF(E_INVALIDARG, url.empty()); - return WinINetDownloadToStream(url, dest, progress); + return WinINetDownloadToStream(url, dest, progress, info); } DownloadResult Download( @@ -309,6 +342,13 @@ namespace AppInstaller::Utility IProgressCallback& progress, std::optional info) { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (TestHooks::s_Download_Function_Override) + { + return (*TestHooks::s_Download_Function_Override)(url, dest, type, progress, info); + } +#endif + THROW_HR_IF(E_INVALIDARG, url.empty()); THROW_HR_IF(E_INVALIDARG, dest.empty()); @@ -368,7 +408,7 @@ namespace AppInstaller::Utility // Use std::ofstream::app to append to previous empty file so that it will not // create a new file and clear motw. std::ofstream outfile(dest, std::ofstream::binary | std::ofstream::app); - return WinINetDownloadToStream(url, outfile, progress); + return WinINetDownloadToStream(url, outfile, progress, info); } using namespace std::string_view_literals; diff --git a/src/AppInstallerCommonCore/MSStoreDownload.cpp b/src/AppInstallerCommonCore/MSStoreDownload.cpp index 4c7dc63253..dc4f409949 100644 --- a/src/AppInstallerCommonCore/MSStoreDownload.cpp +++ b/src/AppInstallerCommonCore/MSStoreDownload.cpp @@ -6,11 +6,12 @@ #include #include "AppInstallerMsixInfo.h" #include "AppInstallerRuntime.h" -#include "winget/Locale.h" +#include "winget/HttpClientHelper.h" #include "winget/JsonUtil.h" +#include "winget/Locale.h" #include "winget/MSStoreDownload.h" +#include "winget/NetworkSettings.h" #include "winget/Rest.h" -#include "winget/HttpClientHelper.h" #include "winget/UserSettings.h" #ifndef WINGET_DISABLE_FOR_FUZZING #include @@ -910,6 +911,12 @@ namespace AppInstaller::MSStore { SFS::RequestParams sfsClientRequest; sfsClientRequest.productRequests = { {std::string{ wuCategoryId }, {}} }; + const auto& proxyUri = AppInstaller::Settings::Network().GetProxyUri(); + if (proxyUri) + { + AICLI_LOG(Core, Info, << "Passing proxy to SFS client " << *proxyUri); + sfsClientRequest.proxy = *proxyUri; + } auto requestResult = GetSfsClientInstance()->GetLatestAppDownloadInfo(sfsClientRequest, appContents); if (!requestResult) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index 4b1da4fb92..d045a88292 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -347,6 +347,21 @@ namespace AppInstaller::Manifest } } } + + // Check AuthInfo validity. For full validation (community repo), authentication type must be none. + if (installer.AuthInfo.Type != Authentication::AuthenticationType::None) + { + if (fullValidation) + { + // Authentication is not supported (must be none) in community repo. + resultErrors.emplace_back(ManifestError::FieldNotSupported, "Authentication"); + } + + if (!installer.AuthInfo.ValidateIntegrity()) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Authentication"); + } + } } // Validate localizations diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index ce448c09f9..2d46f60403 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -388,6 +388,16 @@ namespace AppInstaller::Manifest std::move(fields_v1_9.begin(), fields_v1_9.end(), std::inserter(result, result.end())); } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_10 }) + { + std::vector fields_v1_10 = + { + { "Authentication", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->AuthInfo = {}; auto errors = ValidateAndProcessFields(value, AuthenticationFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->AuthInfo))); GetManifestInstallerPtr(v)->AuthInfo.UpdateRequiredFieldsIfNecessary(); return errors; }, true}, + }; + + std::move(fields_v1_10.begin(), fields_v1_10.end(), std::inserter(result, result.end())); + } } return result; @@ -716,7 +726,38 @@ namespace AppInstaller::Manifest return result; } - + std::vector ManifestYamlPopulator::GetAuthenticationFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_10 }) + { + result = + { + { "AuthenticationType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Type = Authentication::ConvertToAuthenticationType(value.as()); return {}; } }, + { "MicrosoftEntraIdAuthenticationInfo", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->MicrosoftEntraIdInfo.emplace(); return ValidateAndProcessFields(value, MicrosoftEntraIdAuthenticationInfoFieldInfos, VariantManifestPtr(&(variant_ptr(v)->MicrosoftEntraIdInfo.value()))); }}, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetMicrosoftEntraIdAuthenticationInfoFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_10 }) + { + result = + { + { "Resource", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Resource = Utility::Trim(value.as()); return {}; } }, + { "Scope", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Scope = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + std::vector ManifestYamlPopulator::GetShadowRootFieldProcessInfo() { std::vector result; @@ -1071,6 +1112,8 @@ namespace AppInstaller::Manifest NestedInstallerFileFieldInfos = GetNestedInstallerFileFieldProcessInfo(); InstallationMetadataFieldInfos = GetInstallationMetadataFieldProcessInfo(); InstallationMetadataFilesFieldInfos = GetInstallationMetadataFilesFieldProcessInfo(); + AuthenticationFieldInfos = GetAuthenticationFieldInfos(); + MicrosoftEntraIdAuthenticationInfoFieldInfos = GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos, VariantManifestPtr(&(m_manifest.get()))); diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index 62bd3f8d2a..4483fbdaba 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -67,7 +67,12 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view MinimumOSVersion = "MinimumOSVersion"sv; constexpr std::string_view DownloadCommandProhibited = "DownloadCommandProhibited"sv; constexpr std::string_view RepairBehavior = "RepairBehavior"sv; - constexpr std::string_view ArchiveBinariesDependOnPath = "ArchiveBinariesDependOnPath"sv; + constexpr std::string_view ArchiveBinariesDependOnPath = "ArchiveBinariesDependOnPath"sv; + constexpr std::string_view Authentication = "Authentication"sv; + constexpr std::string_view AuthenticationType = "AuthenticationType"sv; + constexpr std::string_view MicrosoftEntraIdAuthenticationInfo = "MicrosoftEntraIdAuthenticationInfo"sv; + constexpr std::string_view MicrosoftEntraIdResource = "Resource"sv; + constexpr std::string_view MicrosoftEntraIdScope = "Scope"sv; // Installer switches constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; @@ -494,6 +499,27 @@ namespace AppInstaller::Manifest::YamlWriter WRITE_PROPERTY_IF_EXISTS(out, DefaultInstallLocation, installationMetadata.DefaultInstallLocation); ProcessInstallationMetadataInstalledFiles(out, installationMetadata.Files); out << YAML::EndMap; + } + + void ProcessAuthentication(YAML::Emitter& out, const Authentication::AuthenticationInfo& authInfo) + { + if (authInfo.Type == Authentication::AuthenticationType::None) + { + return; + } + + out << YAML::Key << Authentication; + out << YAML::BeginMap; + WRITE_PROPERTY(out, AuthenticationType, Authentication::AuthenticationTypeToString(authInfo.Type)); + if (authInfo.MicrosoftEntraIdInfo) + { + out << YAML::Key << MicrosoftEntraIdAuthenticationInfo; + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, MicrosoftEntraIdResource, authInfo.MicrosoftEntraIdInfo->Resource); + WRITE_PROPERTY_IF_EXISTS(out, MicrosoftEntraIdScope, authInfo.MicrosoftEntraIdInfo->Scope); + out << YAML::EndMap; + } + out << YAML::EndMap; } void ProcessDependencies(YAML::Emitter& out, const DependencyList& dependencies) @@ -604,7 +630,8 @@ namespace AppInstaller::Manifest::YamlWriter ProcessNestedInstallerFiles(out, installer.NestedInstallerFiles); ProcessPlatforms(out, installer.Platform); ProcessUnsupportedArguments(out, installer.UnsupportedArguments); - ProcessUnsupportedOSArchitecture(out, installer.UnsupportedOSArchitectures); + ProcessUnsupportedOSArchitecture(out, installer.UnsupportedOSArchitectures); + ProcessAuthentication(out, installer.AuthInfo); } void ProcessInstaller(YAML::Emitter& out, const ManifestInstaller& installer) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h index 1c71b148dd..1ceb4419dd 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -29,11 +29,20 @@ namespace AppInstaller::Utility ConfigurationFile, }; + struct DownloadRequestHeader + { + std::string Name; + std::string Value; + bool IsAuth = false; + }; + // Extra metadata about a download for use by certain downloaders (Delivery Optimization for instance). + // Extra download request headers. struct DownloadInfo { std::string DisplayName; std::string ContentId; + std::vector RequestHeaders; }; // Properties about the downloaded file. diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index cecadb24d8..9f9a63cac0 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -50,6 +50,8 @@ namespace AppInstaller::Runtime SelfPackageRoot, // The location where user downloads are stored. UserProfileDownloads, + // The location where configuration modules are stored. + ConfigurationModules, // The location where checkpoints are stored. CheckpointsLocation, // The location of the CLI executable file. diff --git a/src/AppInstallerCommonCore/Public/winget/Authentication.h b/src/AppInstallerCommonCore/Public/winget/Authentication.h index 5d16f5249f..1d7ee5a0e8 100644 --- a/src/AppInstallerCommonCore/Public/winget/Authentication.h +++ b/src/AppInstallerCommonCore/Public/winget/Authentication.h @@ -14,6 +14,7 @@ namespace AppInstaller::Authentication Unknown, None, MicrosoftEntraId, + MicrosoftEntraIdForAzureBlobStorage, }; std::string_view AuthenticationTypeToString(AuthenticationType in); @@ -45,6 +46,8 @@ namespace AppInstaller::Authentication // Scope is optional std::string Scope; + + bool operator<(const MicrosoftEntraIdAuthenticationInfo& other) const; }; // Authentication info struct used to initialize Authenticator, this is from source information. @@ -53,8 +56,13 @@ namespace AppInstaller::Authentication AuthenticationType Type = AuthenticationType::None; std::optional MicrosoftEntraIdInfo; + bool operator<(const AuthenticationInfo& other) const; + + // Update default values for missing required fields for known authentication type. + void UpdateRequiredFieldsIfNecessary(); + // Validates data integrity against known authentication type. - bool ValidateIntegrity(); + bool ValidateIntegrity() const; }; // Authentication arguments struct used to initialize Authenticator, this is from user input. @@ -131,4 +139,4 @@ namespace AppInstaller::Authentication // Create bearer token from a raw token std::string CreateBearerToken(std::string rawToken); -} \ No newline at end of file +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index e80d076f9c..9812933410 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -3,7 +3,8 @@ #pragma once #include #include -#include +#include +#include #include #include @@ -117,5 +118,7 @@ namespace AppInstaller::Manifest bool DownloadCommandProhibited = false; bool ArchiveBinariesDependOnPath = false; + + Authentication::AuthenticationInfo AuthInfo; }; } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index 4c79f7e3f5..db7775afe2 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -8,7 +8,7 @@ namespace AppInstaller::Manifest { // Add here new manifest pointer types. - using VariantManifestPtr = std::variant*>; + using VariantManifestPtr = std::variant*, AppInstaller::Authentication::AuthenticationInfo*, AppInstaller::Authentication::MicrosoftEntraIdAuthenticationInfo*>; struct ManifestYamlPopulator { @@ -55,6 +55,8 @@ namespace AppInstaller::Manifest std::vector NestedInstallerFileFieldInfos; std::vector InstallationMetadataFieldInfos; std::vector InstallationMetadataFilesFieldInfos; + std::vector AuthenticationFieldInfos; + std::vector MicrosoftEntraIdAuthenticationInfoFieldInfos; // Cache of Installers node and Localization node YAML::Node const* m_p_installersNode = nullptr; @@ -75,6 +77,8 @@ namespace AppInstaller::Manifest std::vector GetNestedInstallerFileFieldProcessInfo(); std::vector GetInstallationMetadataFieldProcessInfo(); std::vector GetInstallationMetadataFilesFieldProcessInfo(); + std::vector GetAuthenticationFieldInfos(); + std::vector GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); // Shadow std::vector GetShadowRootFieldProcessInfo(); diff --git a/src/AppInstallerCommonCore/Public/winget/NetworkSettings.h b/src/AppInstallerCommonCore/Public/winget/NetworkSettings.h index 856055fcc8..ea90ab7f1d 100644 --- a/src/AppInstallerCommonCore/Public/winget/NetworkSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/NetworkSettings.h @@ -16,7 +16,7 @@ namespace AppInstaller::Settings { static NetworkSettings& Instance(); - const std::optional GetProxyUri() const { return m_proxyUri; } + const std::optional& GetProxyUri() const { return m_proxyUri; } // Sets the proxy URI; may do nothing depending on admin settings and group policy void SetProxyUri(const std::optional& proxyUri); @@ -30,4 +30,4 @@ namespace AppInstaller::Settings }; NetworkSettings& Network(); -} \ No newline at end of file +} diff --git a/src/AppInstallerCommonCore/Rest.cpp b/src/AppInstallerCommonCore/Rest.cpp index 7766d13cd1..cf0b63f9ee 100644 --- a/src/AppInstallerCommonCore/Rest.cpp +++ b/src/AppInstallerCommonCore/Rest.cpp @@ -7,10 +7,9 @@ namespace AppInstaller::Rest { - utility::string_t GetRestAPIBaseUri(std::string restApiUri) + utility::string_t GetRestAPIBaseUri(std::string uri) { // Trim - std::string uri = restApiUri; if (!uri.empty()) { uri = AppInstaller::Utility::Trim(uri); diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp index 3a8973d8e9..575ad4e5e7 100644 --- a/src/AppInstallerCommonCore/Runtime.cpp +++ b/src/AppInstallerCommonCore/Runtime.cpp @@ -30,6 +30,7 @@ namespace AppInstaller::Runtime constexpr std::string_view s_PortablePackagesDirectory = "Packages"sv; constexpr std::string_view s_LinksDirectory = "Links"sv; constexpr std::string_view s_FontsInstallDirectory = "Microsoft\\Windows\\Fonts"sv; + constexpr std::string_view s_ConfigurationModulesDirectory = "Configuration\\Modules"sv; // Use production CLSIDs as a surrogate for repository location. #if USE_PROD_CLSIDS constexpr std::string_view s_ImageAssetsDirectoryRelative = "Assets\\WinGet"sv; @@ -240,6 +241,16 @@ namespace AppInstaller::Runtime case PathName::FontsMachineInstallLocation: result.Path = GetKnownFolderPath(FOLDERID_Fonts); break; + case PathName::ConfigurationModules: + result.Path = Settings::User().Get(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_SecureSettings_Base; + result.Path /= s_ConfigurationModulesDirectory; + } + mayBeInProfilePath = true; + break; default: THROW_HR(E_UNEXPECTED); } @@ -316,6 +327,7 @@ namespace AppInstaller::Runtime case PathName::UserProfileDownloads: case PathName::FontsUserInstallLocation: case PathName::FontsMachineInstallLocation: + case PathName::ConfigurationModules: result = GetPathDetailsCommon(path, forDisplay); break; case PathName::SelfPackageRoot: @@ -422,6 +434,7 @@ namespace AppInstaller::Runtime case PathName::UserProfileDownloads: case PathName::FontsUserInstallLocation: case PathName::FontsMachineInstallLocation: + case PathName::ConfigurationModules: result = GetPathDetailsCommon(path, forDisplay); break; case PathName::SelfPackageRoot: diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp index c7e796bb16..c7f0bfd79c 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -30,7 +30,7 @@ namespace AppInstaller::Repository::Rest namespace { - HttpClientHelper::HttpRequestHeaders GetHeaders(std::optional customHeader, std::string_view caller) + HttpClientHelper::HttpRequestHeaders GetHeaders(const std::optional& customHeader, std::string_view caller) { HttpClientHelper::HttpRequestHeaders headers; @@ -135,7 +135,7 @@ namespace AppInstaller::Repository::Rest return *commonVersions.rbegin(); } - Schema::IRestClient::Information RestClient::GetInformation(const std::string& restApi, std::optional customHeader, std::string_view caller, const HttpClientHelper& helper) + Schema::IRestClient::Information RestClient::GetInformation(const std::string& restApi, const std::optional& customHeader, std::string_view caller, const HttpClientHelper& helper) { utility::string_t restEndpoint = AppInstaller::Rest::GetRestAPIBaseUri(restApi); THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(restEndpoint)); @@ -185,14 +185,19 @@ namespace AppInstaller::Repository::Rest THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } - RestClient RestClient::Create(const std::string& restApi, std::optional customHeader, std::string_view caller, const HttpClientHelper& helper, const Authentication::AuthenticationArguments& authArgs) + RestClient RestClient::Create( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const HttpClientHelper& helper, + const Schema::IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs) { utility::string_t restEndpoint = AppInstaller::Rest::GetRestAPIBaseUri(restApi); THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(restEndpoint)); auto headers = GetHeaders(customHeader, caller); - IRestClient::Information information = GetInformationInternal(restEndpoint, headers, helper); std::optional latestCommonVersion = GetLatestCommonVersion(information.ServerSupportedVersions, WingetSupportedContracts); THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !latestCommonVersion); diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.h b/src/AppInstallerRepositoryCore/Rest/RestClient.h index 66d299155c..fc02291eb0 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.h +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.h @@ -28,7 +28,7 @@ namespace AppInstaller::Repository::Rest static std::optional GetLatestCommonVersion(const std::vector& serverSupportedVersions, const std::set& wingetSupportedVersions); // Responsible for getting the source information contracts with minimal validation. Does not try to create a rest interface out of it. - static Schema::IRestClient::Information GetInformation(const std::string& restApi, std::optional customHeader, std::string_view caller, const Http::HttpClientHelper& helper); + static Schema::IRestClient::Information GetInformation(const std::string& restApi, const std::optional& customHeader, std::string_view caller, const Http::HttpClientHelper& helper); static std::unique_ptr GetSupportedInterface( const std::string& restApi, @@ -39,7 +39,13 @@ namespace AppInstaller::Repository::Rest const Http::HttpClientHelper& helper); // Creates the rest client. Full validation performed (just as opening the source) - static RestClient Create(const std::string& restApi, std::optional customHeader, std::string_view caller, const Http::HttpClientHelper& helper, const Authentication::AuthenticationArguments& authArgs = {}); + static RestClient Create( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const Http::HttpClientHelper& helper, + const Schema::IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs = {}); private: RestClient(std::unique_ptr supportedInterface, std::string sourceIdentifier); diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp index da6cc0850e..b30e18d882 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp @@ -50,7 +50,7 @@ namespace AppInstaller::Repository::Rest std::shared_ptr Open(IProgressCallback&) override { Initialize(); - RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper, m_authArgs); + RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper, m_restClientInformation, m_authArgs); return std::make_shared(m_details, m_information, std::move(restClient)); } @@ -61,28 +61,29 @@ namespace AppInstaller::Repository::Rest [&]() { m_httpClientHelper.SetPinningConfiguration(m_details.CertificatePinningConfiguration); - auto sourceInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); + m_restClientInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); - m_details.Identifier = sourceInformation.SourceIdentifier; + m_details.Identifier = m_restClientInformation.SourceIdentifier; - m_information.UnsupportedPackageMatchFields = sourceInformation.UnsupportedPackageMatchFields; - m_information.RequiredPackageMatchFields = sourceInformation.RequiredPackageMatchFields; - m_information.UnsupportedQueryParameters = sourceInformation.UnsupportedQueryParameters; - m_information.RequiredQueryParameters = sourceInformation.RequiredQueryParameters; + m_information.UnsupportedPackageMatchFields = m_restClientInformation.UnsupportedPackageMatchFields; + m_information.RequiredPackageMatchFields = m_restClientInformation.RequiredPackageMatchFields; + m_information.UnsupportedQueryParameters = m_restClientInformation.UnsupportedQueryParameters; + m_information.RequiredQueryParameters = m_restClientInformation.RequiredQueryParameters; - m_information.SourceAgreementsIdentifier = sourceInformation.SourceAgreementsIdentifier; - for (auto const& agreement : sourceInformation.SourceAgreements) + m_information.SourceAgreementsIdentifier = m_restClientInformation.SourceAgreementsIdentifier; + for (auto const& agreement : m_restClientInformation.SourceAgreements) { m_information.SourceAgreements.emplace_back(agreement.Label, agreement.Text, agreement.Url); } - m_information.Authentication = sourceInformation.Authentication; + m_information.Authentication = m_restClientInformation.Authentication; }); } SourceDetails m_details; Http::HttpClientHelper m_httpClientHelper; SourceInformation m_information; + Schema::IRestClient::Information m_restClientInformation; std::optional m_customHeader; std::string m_caller; Authentication::AuthenticationArguments m_authArgs; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp index d077a8151a..de4819f28e 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp @@ -14,6 +14,33 @@ namespace AppInstaller::Repository::Rest::Schema constexpr std::string_view MicrosoftEntraIdAuthenticationInfo = "MicrosoftEntraIdAuthenticationInfo"sv; constexpr std::string_view MicrosoftEntraId_Resource = "Resource"sv; constexpr std::string_view MicrosoftEntraId_Scope = "Scope"sv; + + Authentication::AuthenticationType ConvertToAuthenticationTypeForSource(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + Authentication::AuthenticationType result = Authentication::AuthenticationType::Unknown; + + if (inStrLower == "none") + { + result = Authentication::AuthenticationType::None; + } + else if (inStrLower == "microsoftentraid") + { + result = Authentication::AuthenticationType::MicrosoftEntraId; + } + + return result; + } + + Authentication::AuthenticationType ConvertToAuthenticationTypeForInstaller(std::string_view in, Manifest::ManifestVer manifestVersion) + { + if (manifestVersion >= Manifest::ManifestVer{ Manifest::s_ManifestVersionV1_10 }) + { + return Authentication::ConvertToAuthenticationType(in); + } + + return Authentication::AuthenticationType::Unknown; + } } // The authentication info json looks like below: @@ -24,7 +51,7 @@ namespace AppInstaller::Repository::Rest::Schema // "Scope" : "test" // } // } - Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, std::optional) + Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, ParseAuthenticationInfoType parseType, std::optional manifestVersion) { auto authenticationObject = JSON::GetJsonValueFromNode(dataObject, JSON::GetUtilityString(Authentication)); if (!authenticationObject) @@ -46,7 +73,15 @@ namespace AppInstaller::Repository::Rest::Schema auto authenticationTypeString = JSON::GetRawStringValueFromJsonNode(authenticationObjectNode, JSON::GetUtilityString(AuthenticationType)); // AuthenticationType required if Authentication exists and is not null. THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, !JSON::IsValidNonEmptyStringValue(authenticationTypeString)); - result.Type = Authentication::ConvertToAuthenticationType(authenticationTypeString.value()); + if (parseType == ParseAuthenticationInfoType::Source) + { + result.Type = ConvertToAuthenticationTypeForSource(authenticationTypeString.value()); + } + else if (parseType == ParseAuthenticationInfoType::Installer) + { + THROW_HR_IF(E_INVALIDARG, !manifestVersion); + result.Type = ConvertToAuthenticationTypeForInstaller(authenticationTypeString.value(), manifestVersion.value()); + } // Parse MicrosoftEntraId info auto microsoftEntraIdInfoObject = JSON::GetJsonValueFromNode(authenticationObjectNode, JSON::GetUtilityString(MicrosoftEntraIdAuthenticationInfo)); @@ -70,6 +105,7 @@ namespace AppInstaller::Repository::Rest::Schema result.MicrosoftEntraIdInfo = std::move(microsoftEntraIdInfo); } + result.UpdateRequiredFieldsIfNecessary(); THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, !result.ValidateIntegrity()); return result; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.h b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.h index 7c8c8758d8..ce68d4bcdc 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.h @@ -7,6 +7,12 @@ namespace AppInstaller::Repository::Rest::Schema { + enum class ParseAuthenticationInfoType + { + Source, + Installer, + }; + // Parses AuthenticationInfo from json object. // This could be used for installer level parsing as well in manifest deserializer (currently not supported, manifestVersion not used). // The authentication info json looks like below: @@ -17,5 +23,5 @@ namespace AppInstaller::Repository::Rest::Schema // "Scope" : "test" // } // } - Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, std::optional manifestVersion = {}); + Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, ParseAuthenticationInfoType parseType, std::optional manifestVersion = {}); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp index 6a272de037..5d67cc6b90 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp @@ -124,7 +124,7 @@ namespace AppInstaller::Repository::Rest::Schema info.RequiredQueryParameters = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(RequiredQueryParameters)); info.UnsupportedQueryParameters = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(UnsupportedQueryParameters)); - info.Authentication = ParseAuthenticationInfo(dataValue); + info.Authentication = ParseAuthenticationInfo(dataValue, ParseAuthenticationInfoType::Source); return info; } diff --git a/src/AppInstallerSharedLib/Public/AppInstallerLogging.h b/src/AppInstallerSharedLib/Public/AppInstallerLogging.h index a7ac612abb..63946a6327 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerLogging.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerLogging.h @@ -141,7 +141,7 @@ namespace AppInstaller::Logging void DisableChannel(Channel channel); // Sets the enabled level. - // All levels higher than this level will be enabled. + // All levels above this level will be enabled. // For example; SetLevel(Verbose) will enable all logs. void SetLevel(Level level); diff --git a/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj b/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj index 52957466c2..ca9fdb50ed 100644 --- a/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj +++ b/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj @@ -2,34 +2,30 @@ Exe - net6.0-windows10.0.22000.0 + net8.0-windows10.0.22000.0 enable enable 10.0.17763.0 - x64;x86;arm;arm64 + x64;x86;arm64 $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ true - win10-x64;win10-x86;win10-arm;win10-arm64 + win-x64;win-x86;win-arm64 - 10.0.22000.52 + 10.0.22000.53 - win10-x64 + win-x64 - win10-x86 - - - - win10-arm + win-x86 - win10-arm64 + win-arm64 diff --git a/src/IndexCreationTool/IndexCreationTool.csproj b/src/IndexCreationTool/IndexCreationTool.csproj index 79a4d0812b..4025eeb1f4 100644 --- a/src/IndexCreationTool/IndexCreationTool.csproj +++ b/src/IndexCreationTool/IndexCreationTool.csproj @@ -1,8 +1,8 @@ - + Exe - net6.0 + net8.0 $(SolutionDir)$(Platform)\$(Configuration)\IndexCreationTool\ x64;x86 diff --git a/src/LocalhostWebServer/LocalhostWebServer.csproj b/src/LocalhostWebServer/LocalhostWebServer.csproj index b4ae8ba832..e963674a20 100644 --- a/src/LocalhostWebServer/LocalhostWebServer.csproj +++ b/src/LocalhostWebServer/LocalhostWebServer.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net8.0 $(SolutionDir)$(Platform)\$(Configuration)\LocalhostWebServer\ x64;x86 diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 b/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 index c445699d5e..c3c24d8bac 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 +++ b/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 @@ -9,13 +9,13 @@ param( # Copy the winmd into the unit test directory since it will be needed for marshalling $Local:winmdSourcePath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd" -$Local:winmdTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.winmd" +$Local:winmdTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net8.0-windows10.0.22000.0\Microsoft.Management.Configuration.winmd" Copy-Item $Local:winmdSourcePath $Local:winmdTargetPath -Force # Copy the OOP helper dll into the unit test directory to make activation look the same as in-proc $Local:dllSourcePath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.OutOfProc\Microsoft.Management.Configuration.OutOfProc.dll" -$Local:dllTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.dll" +$Local:dllTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net8.0-windows10.0.22000.0\Microsoft.Management.Configuration.dll" Copy-Item $Local:dllSourcePath $Local:dllTargetPath -Force diff --git a/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj b/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj index b21a17f4e3..71319bc28e 100644 --- a/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj +++ b/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 $(DotNetVersion)-windows10.0.22000.0 enable @@ -21,7 +21,7 @@ - 10.0.22000.52 + 10.0.22000.53 @@ -43,18 +43,13 @@ - + contentFiles all - - - - - diff --git a/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj b/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj index c6d7ba6d5d..3671eb08ac 100644 --- a/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj +++ b/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj @@ -1,7 +1,7 @@ - net6.0-windows10.0.22000.0 + net8.0-windows10.0.22000.0 AnyCpu enable enable @@ -11,7 +11,7 @@ - 10.0.22000.52 + 10.0.22000.53 diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs index 58c4b1491c..5450cc13b8 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs @@ -31,7 +31,7 @@ public OutOfProcAttribute() // The test runner is located somewhere like this: // C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform // and the command line from there is: - // .\vstest.console.exe "\src\x64\Debug\Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.UnitTests.dll" --TestCaseFilter:Category=OutOfProc + // .\vstest.console.exe "\src\x64\Debug\Microsoft.Management.Configuration.UnitTests\net8.0-windows10.0.22000.0\Microsoft.Management.Configuration.UnitTests.dll" --TestCaseFilter:Category=OutOfProc } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj b/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj index fa1c9c9a77..4cab06265a 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj +++ b/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj @@ -1,16 +1,16 @@ - net6.0-windows10.0.22000.0 + net8.0-windows10.0.22000.0 enable 10.0.17763.0 x64;x86;arm64 $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - win10-x64;win10-x86;win10-arm;win10-arm64 + win-x64;win-x86;win-arm64 - 10.0.22000.52 + 10.0.22000.53 @@ -25,6 +25,9 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -39,7 +42,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs index 04de11440c..a2819bd93e 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -607,7 +607,7 @@ public void TestSet_Serialize_0_3() Assert.Equal(2, set.Units.Count); this.VerifyValueSet(set.Metadata, new KeyValuePair("description", "FakeSetDescription")); - this.VerifyValueSet(set.Variables, new("var1", "Test1"), new("var2", 42)); + this.VerifyValueSet(set.Variables, new ("var1", "Test1"), new ("var2", 42)); Assert.Equal(2, set.Parameters.Count); this.VerifyParameter(set.Parameters[0], "param1", Windows.Foundation.PropertyType.String, true); @@ -615,17 +615,17 @@ public void TestSet_Serialize_0_3() Assert.Equal("FakeModule/FakeResource", set.Units[0].Type); Assert.Equal("TestId", set.Units[0].Identifier); - this.VerifyValueSet(set.Units[0].Metadata, new("description", "FakeDescription"), new("allowPrerelease", true), new("securityContext", "elevated")); - this.VerifyValueSet(set.Units[0].Settings, new("TestString", "Hello"), new("TestBool", false), new("TestInt", 1234)); + this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("securityContext", "elevated")); + this.VerifyValueSet(set.Units[0].Settings, new ("TestString", "Hello"), new ("TestBool", false), new ("TestInt", 1234)); Assert.Equal("FakeModule2/FakeResource2", set.Units[1].Type); Assert.Equal("TestId2", set.Units[1].Identifier); this.VerifyStringArray(set.Units[1].Dependencies, "TestId", "dependency2", "dependency3"); - this.VerifyValueSet(set.Units[1].Metadata, new("description", "FakeDescription2"), new("securityContext", "elevated")); + this.VerifyValueSet(set.Units[1].Metadata, new ("description", "FakeDescription2"), new ("securityContext", "elevated")); ValueSet mapping = new ValueSet(); mapping.Add("Key", "TestValue"); - this.VerifyValueSet(set.Units[1].Settings, new("TestString", "Bye"), new("TestBool", true), new("TestInt", 4321), new("Mapping", mapping)); + this.VerifyValueSet(set.Units[1].Settings, new ("TestString", "Bye"), new ("TestBool", true), new ("TestInt", 4321), new ("Mapping", mapping)); } /// diff --git a/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj b/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj index 288b813059..b0260252c1 100644 --- a/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj +++ b/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj @@ -1,7 +1,7 @@ - net6.0-windows + net8.0-windows $(SolutionDir)$(Platform)\$(Configuration)\Microsoft.Management.Deployment.Projection\ false x64;x86;arm64 @@ -10,7 +10,7 @@ - 10.0.22000.52 + 10.0.22000.53 diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index 08338dd306..41f796d272 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -464,6 +464,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation return Microsoft::Management::Deployment::AuthenticationType::None; case ::AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId: return Microsoft::Management::Deployment::AuthenticationType::MicrosoftEntraId; + case ::AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage: + return Microsoft::Management::Deployment::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage; } return Microsoft::Management::Deployment::AuthenticationType::Unknown; diff --git a/src/Microsoft.Management.Deployment/DownloadOptions.cpp b/src/Microsoft.Management.Deployment/DownloadOptions.cpp index fbd73bc936..4a8c7317c7 100644 --- a/src/Microsoft.Management.Deployment/DownloadOptions.cpp +++ b/src/Microsoft.Management.Deployment/DownloadOptions.cpp @@ -97,6 +97,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation void DownloadOptions::CorrelationData(hstring const& value) { m_correlationData = value; + } + winrt::Microsoft::Management::Deployment::AuthenticationArguments DownloadOptions::AuthenticationArguments() + { + return m_authenticationArguments; + } + void DownloadOptions::AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value) + { + m_authenticationArguments = value; } CoCreatableMicrosoftManagementDeploymentClass(DownloadOptions); } diff --git a/src/Microsoft.Management.Deployment/DownloadOptions.h b/src/Microsoft.Management.Deployment/DownloadOptions.h index 77530007f9..bc0f95d7f3 100644 --- a/src/Microsoft.Management.Deployment/DownloadOptions.h +++ b/src/Microsoft.Management.Deployment/DownloadOptions.h @@ -30,7 +30,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool AcceptPackageAgreements(); void AcceptPackageAgreements(bool value); hstring CorrelationData(); - void CorrelationData(hstring const& value); + void CorrelationData(hstring const& value); + winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); + void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -43,7 +45,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool m_allowHashMismatch = false; bool m_skipDependencies = false; bool m_acceptPackageAgreements = true; - std::wstring m_correlationData = L""; + std::wstring m_correlationData = L""; + winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; #endif }; } diff --git a/src/Microsoft.Management.Deployment/InstallOptions.cpp b/src/Microsoft.Management.Deployment/InstallOptions.cpp index 3d491a29e5..09189fd76e 100644 --- a/src/Microsoft.Management.Deployment/InstallOptions.cpp +++ b/src/Microsoft.Management.Deployment/InstallOptions.cpp @@ -160,5 +160,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation { return m_skipDependencies; } + winrt::Microsoft::Management::Deployment::AuthenticationArguments InstallOptions::AuthenticationArguments() + { + return m_authenticationArguments; + } + void InstallOptions::AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value) + { + m_authenticationArguments = value; + } CoCreatableMicrosoftManagementDeploymentClass(InstallOptions); } diff --git a/src/Microsoft.Management.Deployment/InstallOptions.h b/src/Microsoft.Management.Deployment/InstallOptions.h index b5f2fb0a6e..15913bf5ce 100644 --- a/src/Microsoft.Management.Deployment/InstallOptions.h +++ b/src/Microsoft.Management.Deployment/InstallOptions.h @@ -44,6 +44,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation void AcceptPackageAgreements(bool value); bool SkipDependencies(); void SkipDependencies(bool value); + winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); + void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -65,6 +67,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool m_force = false; bool m_acceptPackageAgreements = true; bool m_skipDependencies = false; + winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; #endif }; } diff --git a/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp b/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp index 01f55b9df2..f6e53ecd04 100644 --- a/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp +++ b/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp @@ -2,7 +2,8 @@ // Licensed under the MIT License. #include "pch.h" #include "PackageInstallerInfo.h" -#include "PackageInstallerInfo.g.cpp" +#include "PackageInstallerInfo.g.cpp" +#include "AuthenticationInfo.h" #include "Converters.h" #include @@ -37,4 +38,15 @@ namespace winrt::Microsoft::Management::Deployment::implementation { return GetDeploymentElevationRequirement(m_manifestInstaller.ElevationRequirement); } + winrt::Microsoft::Management::Deployment::AuthenticationInfo PackageInstallerInfo::AuthenticationInfo() + { + std::call_once(m_authenticationInfoOnceFlag, + [&]() + { + auto authenticationInfo = winrt::make_self>(); + authenticationInfo->Initialize(m_manifestInstaller.AuthInfo); + m_authenticationInfo = *authenticationInfo; + }); + return m_authenticationInfo; + } } diff --git a/src/Microsoft.Management.Deployment/PackageInstallerInfo.h b/src/Microsoft.Management.Deployment/PackageInstallerInfo.h index 6a0021b6cf..de09dd32bd 100644 --- a/src/Microsoft.Management.Deployment/PackageInstallerInfo.h +++ b/src/Microsoft.Management.Deployment/PackageInstallerInfo.h @@ -20,11 +20,15 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageInstallerScope Scope(); hstring Locale(); // Contract 6.0 - winrt::Microsoft::Management::Deployment::ElevationRequirement ElevationRequirement(); - + winrt::Microsoft::Management::Deployment::ElevationRequirement ElevationRequirement(); + // Contract 12.0 + winrt::Microsoft::Management::Deployment::AuthenticationInfo AuthenticationInfo(); + #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: - ::AppInstaller::Manifest::ManifestInstaller m_manifestInstaller; + ::AppInstaller::Manifest::ManifestInstaller m_manifestInstaller; + std::once_flag m_authenticationInfoOnceFlag; + winrt::Microsoft::Management::Deployment::AuthenticationInfo m_authenticationInfo{ nullptr }; #endif }; } diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index e0b3c7c87d..836a31d0c9 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -7,6 +7,7 @@ #include "ComContext.h" #include "ExecutionContext.h" #include "Workflows/WorkflowBase.h" +#include #include #include #include "Commands/COMCommand.h" @@ -572,6 +573,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation { context->Args.AddArg(Execution::Args::Type::SkipDependencies); } + + if (options.AuthenticationArguments()) + { + context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); + context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); + } } else { @@ -660,6 +667,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation { context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); } + + if (options.AuthenticationArguments()) + { + context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); + context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); + } } } @@ -709,6 +722,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation { context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(repairScope)); } + + if (options.AuthenticationArguments()) + { + context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); + context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); + } } } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 943177fc45..29f2e9166a 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -515,6 +515,12 @@ namespace Microsoft.Management.Deployment /// The package installer elevation requirement. ElevationRequirement ElevationRequirement { get; }; } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication info from the package installer. + AuthenticationInfo AuthenticationInfo { get; }; + } }; /// The installed status type. The values need to match InstalledStatusType from winget/RepositorySearch.h. @@ -813,6 +819,10 @@ namespace Microsoft.Management.Deployment Unknown, None, MicrosoftEntraId, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + MicrosoftEntraIdForAzureBlobStorage, + } }; /// Microsoft Entra Id related authentication info. @@ -927,7 +937,6 @@ namespace Microsoft.Management.Deployment [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] { - /// Authentication arguments used in authentication flow during package catalog operations if applicable. /// This is user or caller input. AuthenticationArguments AuthenticationArguments; @@ -1047,11 +1056,11 @@ namespace Microsoft.Management.Deployment [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] { - // The set of allowed Architectures, in preference order, that will be considered for - // the install operation. Initially the vector contains the default allowed architectures - // in the default preference order for the current system. It is allowed to have repeated - // values in the list, to make prepending a preference override easier. Instances of an - // architecture after the first will simply be ignored. + /// The set of allowed Architectures, in preference order, that will be considered for + /// the install operation. Initially the vector contains the default allowed architectures + /// in the default preference order for the current system. It is allowed to have repeated + /// values in the list, to make prepending a preference override easier. Instances of an + /// architecture after the first will simply be ignored. Windows.Foundation.Collections.IVector AllowedArchitectures { get; }; } @@ -1082,12 +1091,18 @@ namespace Microsoft.Management.Deployment [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] { - // Skip installing the dependencies for the package. + /// Skip installing the dependencies for the package. Boolean SkipDependencies; /// The package installer type. PackageInstallerType InstallerType; } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication arguments used when downloading the package installer if authentication is required. + AuthenticationArguments AuthenticationArguments; + } } [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] @@ -1181,6 +1196,12 @@ namespace Microsoft.Management.Deployment /// Used by a caller to correlate the download with a caller's data. /// The string must be JSON encoded. String CorrelationData; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication arguments used when downloading the package installer if authentication is required. + AuthenticationArguments AuthenticationArguments; + } } [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] @@ -1239,6 +1260,12 @@ namespace Microsoft.Management.Deployment /// Bypasses the Disabled Store Policy Boolean BypassIsStoreClientBlockedPolicyCheck; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication arguments used when downloading the package installer if authentication is required. + AuthenticationArguments AuthenticationArguments; + } } /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Manifest::Documentation diff --git a/src/Microsoft.Management.Deployment/RepairOptions.cpp b/src/Microsoft.Management.Deployment/RepairOptions.cpp index e321c69483..48b065109e 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.cpp +++ b/src/Microsoft.Management.Deployment/RepairOptions.cpp @@ -108,5 +108,15 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_force = value; } + winrt::Microsoft::Management::Deployment::AuthenticationArguments RepairOptions::AuthenticationArguments() + { + return m_authenticationArguments; + } + + void RepairOptions::AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value) + { + m_authenticationArguments = value; + } + CoCreatableMicrosoftManagementDeploymentClass(RepairOptions); } diff --git a/src/Microsoft.Management.Deployment/RepairOptions.h b/src/Microsoft.Management.Deployment/RepairOptions.h index 2b083bef6a..909cf9f6f2 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.h +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -29,7 +29,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation void BypassIsStoreClientBlockedPolicyCheck(bool value); bool Force(); void Force(bool value); - + winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); + void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -42,6 +43,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation std::wstring m_correlationData = L""; winrt::Microsoft::Management::Deployment::PackageRepairScope m_packageRepairScope = winrt::Microsoft::Management::Deployment::PackageRepairScope::Any; winrt::Microsoft::Management::Deployment::PackageRepairMode m_packageRepairMode = winrt::Microsoft::Management::Deployment::PackageRepairMode::Default; + winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; #endif }; } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj index 2df497ed92..ac1388d120 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj @@ -3,7 +3,7 @@ - net6.0-windows10.0.22000.0 + net8.0-windows10.0.22000.0 false net48 Debug;Release;ReleaseStatic @@ -11,7 +11,7 @@ - 10.0.22000.52 + 10.0.22000.53 @@ -52,7 +52,7 @@ - win10 + win diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj index 3998fe740f..13bd34b2ea 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj @@ -4,7 +4,7 @@ 10.0.22000.0 - net6.0-windows$(TargetWindowsVersion) + net8.0-windows$(TargetWindowsVersion) false net48 Debug;Release;ReleaseStatic @@ -12,7 +12,7 @@ - 10.0.22000.52 + 10.0.22000.53 @@ -94,7 +94,7 @@ - win10 + win diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 index 3ac00bf044..f6b95ab9fe 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 @@ -9,7 +9,7 @@ # Script module or binary module file associated with this manifest. RootModule = if ($PSEdition -like 'Core') { - "net6.0-windows10.0.22000.0\Microsoft.WinGet.Client.Cmdlets.dll" + "net8.0-windows10.0.22000.0\Microsoft.WinGet.Client.Cmdlets.dll" } else { diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj index eebc4a756c..5badbfe17c 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj @@ -2,7 +2,7 @@ - net6.0-windows10.0.22000 + net8.0-windows10.0.22000 false $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ true @@ -15,7 +15,7 @@ - 10.0.22000.52 + 10.0.22000.53 diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj index ebddf5f91b..93b1fdb179 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj @@ -2,7 +2,7 @@ - net6.0-windows10.0.22000 + net8.0-windows10.0.22000 false enable $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ @@ -14,7 +14,7 @@ - 10.0.22000.52 + 10.0.22000.53 diff --git a/src/PowerShell/Microsoft.WinGet.Configuration/ModuleFiles/Microsoft.WinGet.Configuration.psd1 b/src/PowerShell/Microsoft.WinGet.Configuration/ModuleFiles/Microsoft.WinGet.Configuration.psd1 index b652ad1df9..9349bc6576 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration/ModuleFiles/Microsoft.WinGet.Configuration.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Configuration/ModuleFiles/Microsoft.WinGet.Configuration.psd1 @@ -7,7 +7,7 @@ CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation. All rights reserved.' Description = 'PowerShell Module for the Windows Package Manager Configuration.' - PowerShellVersion = '7.2.8' + PowerShellVersion = '7.4.6' FunctionsToExport = @() AliasesToExport = @('cmpwgc', 'cnwgc', 'ctwgcy', 'gwgc', 'gwgcd', 'iwgc', 'rwgch', 'sawgc', 'spwgc','twgc') diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 index 8f69e334c5..4e5cedb3d9 100644 --- a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 +++ b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -202,7 +202,7 @@ if ($moduleToConfigure.HasFlag([ModuleType]::Client)) "WindowsPackageManager\WindowsPackageManager.dll" "UndockedRegFreeWinRT\winrtact.dll" ) - $module.AddArchSpecificFiles($additionalFiles, "net6.0-windows10.0.22000.0\SharedDependencies", $BuildRoot, $Configuration) + $module.AddArchSpecificFiles($additionalFiles, "net8.0-windows10.0.22000.0\SharedDependencies", $BuildRoot, $Configuration) $module.AddArchSpecificFiles($additionalFiles, "net48\SharedDependencies", $BuildRoot, $Configuration) $modules += $module } @@ -226,7 +226,7 @@ if ($moduleToConfigure.HasFlag([ModuleType]::Configuration)) ) $module.AddArchSpecificFiles($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) $additionalFiles = @( - "Microsoft.Management.Configuration.Projection\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.Projection.dll" + "Microsoft.Management.Configuration.Projection\net8.0-windows10.0.22000.0\Microsoft.Management.Configuration.Projection.dll" ) $module.AddAnyCpuSpecificFilesToArch($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) $modules += $module diff --git a/src/SfsClient/SfsClient.vcxproj b/src/SfsClient/SfsClient.vcxproj index 5f1e0d4801..4135d5eefb 100644 --- a/src/SfsClient/SfsClient.vcxproj +++ b/src/SfsClient/SfsClient.vcxproj @@ -241,7 +241,7 @@ - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";WIN32;_DEBUG;%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";WIN32;_DEBUG;%(PreprocessorDefinitions) TurnOffAllWarnings ProgramDatabase stdcpp17 @@ -257,7 +257,7 @@ - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";WIN32;NDEBUG;%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";WIN32;NDEBUG;%(PreprocessorDefinitions) Level3 ProgramDatabase stdcpp17 @@ -274,7 +274,7 @@ - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";WIN32;NDEBUG;%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";WIN32;NDEBUG;%(PreprocessorDefinitions) Level3 ProgramDatabase stdcpp17 @@ -294,7 +294,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";NDEBUG;%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions) Level3 $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) true @@ -304,7 +304,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";NDEBUG;%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions) Level3 $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) MultiThreaded @@ -315,7 +315,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) Level3 $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) true @@ -325,7 +325,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) Level3 $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) MultiThreaded @@ -336,7 +336,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) Level3 $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) true @@ -346,7 +346,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";NDEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) Level3 $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) MultiThreaded @@ -357,7 +357,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";_DEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";_DEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) TurnOffAllWarnings $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) true @@ -367,7 +367,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";_DEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";_DEBUG;%(PreprocessorDefinitions)%(PreprocessorDefinitions) TurnOffAllWarnings $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) true @@ -377,7 +377,7 @@ stdcpp17 NotUsing - CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.0.0";_DEBUG;%(PreprocessorDefinitions) + CURL_STATICLIB;GUID_WINDOWS;SFS_VERSION="1.1.0";_DEBUG;%(PreprocessorDefinitions) TurnOffAllWarnings $(ProjectDir)sfs-client\client\include\sfsclient;%(AdditionalIncludeDirectories) true diff --git a/src/SfsClient/readme.md b/src/SfsClient/readme.md index 382a316136..84fcea431b 100644 --- a/src/SfsClient/readme.md +++ b/src/SfsClient/readme.md @@ -1,10 +1,15 @@ ## SfsClient -Do not change code under the sfs-client directory; it contains sfs-client source code from commit [be733af](https://github.com/microsoft/sfs-client/commits/be733af). -It is created using git subtree command: +Do not change code under the sfs-client directory; it contains sfs-client source code from release 1.1.0 (https://github.com/microsoft/sfs-client/releases/tag/1.1.0). +It was initially created using git subtree command: ``` git subtree add --prefix=src/SfsClient/sfs-client https://github.com/microsoft/sfs-client.git be733af9e5c8e9227f2018ff618800bf08a31180 --squash ``` +Then updated to release 1.1.0 using: +``` + git subtree pull -P src/SfsClient/sfs-client https://github.com/microsoft/sfs-client 1.1.0 --squash +``` + ### Update To update, run the following command, then update the above commit for reference. 'master' can be replaced with the appropriate commit spec as desired. diff --git a/src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml b/src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml new file mode 100644 index 0000000000..990b0603bf --- /dev/null +++ b/src/SfsClient/sfs-client/.github/workflows/initialize-codeql/action.yml @@ -0,0 +1,12 @@ +name: Initialize CodeQL + +description: Initializes CodeQL action to be used in build workflows + +runs: + using: "composite" + + steps: + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: cpp \ No newline at end of file diff --git a/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml b/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml index 1d12f11723..59ee8521c9 100644 --- a/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml +++ b/src/SfsClient/sfs-client/.github/workflows/main-build-ubuntu.yml @@ -5,8 +5,10 @@ on: branches: [ "main" ] # Permissions and environment values to be able to update the dependency graph with vcpkg information +# and to enable the writing/uploading of CodeQL scan results permissions: contents: write + security-events: write env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -19,6 +21,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Initialize CodeQL + uses: ./.github/workflows/initialize-codeql + - name: Setup run: source ./scripts/setup.sh @@ -36,3 +41,6 @@ jobs: run: | ./scripts/build.sh --build-type Release ./scripts/test.sh --output-on-failure + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml b/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml index 1a57d5689e..0c2a6cf653 100644 --- a/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml +++ b/src/SfsClient/sfs-client/.github/workflows/main-build-windows.yml @@ -5,8 +5,10 @@ on: branches: [ "main" ] # Permissions and environment values to be able to update the dependency graph with vcpkg information +# and to enable the writing/uploading of CodeQL scan results permissions: contents: write + security-events: write env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -19,12 +21,15 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Initialize CodeQL + uses: ./.github/workflows/initialize-codeql + - name: Install Winget uses: ./.github/workflows/install-winget - name: Setup shell: pwsh - run: .\scripts\Setup.ps1 -NoBuildTools + run: .\scripts\Setup.ps1 - name: Build and Test (no test overrides) shell: pwsh @@ -43,3 +48,6 @@ jobs: run: | .\scripts\Build.ps1 -BuildType Release .\scripts\Test.ps1 -OutputOnFailure + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/src/SfsClient/sfs-client/.github/workflows/pr.yml b/src/SfsClient/sfs-client/.github/workflows/pr.yml index 8b62920943..f3740d36c4 100644 --- a/src/SfsClient/sfs-client/.github/workflows/pr.yml +++ b/src/SfsClient/sfs-client/.github/workflows/pr.yml @@ -22,12 +22,15 @@ jobs: core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + - name: Initialize CodeQL + uses: ./.github/workflows/initialize-codeql + - name: Install Winget uses: ./.github/workflows/install-winget - name: Setup shell: pwsh - run: .\scripts\Setup.ps1 -NoBuildTools + run: .\scripts\Setup.ps1 - name: Check formatting shell: pwsh @@ -45,6 +48,9 @@ jobs: .\scripts\Build.ps1 -EnableTestOverrides .\scripts\Test.ps1 -OutputOnFailure + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + build-ubuntu: runs-on: ubuntu-latest @@ -58,6 +64,9 @@ jobs: core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + - name: Initialize CodeQL + uses: ./.github/workflows/initialize-codeql + - name: Setup run: source ./scripts/setup.sh @@ -73,3 +82,6 @@ jobs: run: | ./scripts/build.sh --enable-test-overrides ./scripts/test.sh --output-on-failure + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/src/SfsClient/sfs-client/CMakeLists.txt b/src/SfsClient/sfs-client/CMakeLists.txt index 0d33a25954..31bb1010cd 100644 --- a/src/SfsClient/sfs-client/CMakeLists.txt +++ b/src/SfsClient/sfs-client/CMakeLists.txt @@ -27,7 +27,7 @@ endif() # 1. MAJOR version when you make incompatible API changes # 2. MINOR version when you add functionality in a backward compatible manner # 3. PATCH version when you make backward compatible bug fixes -set(SFS_LIBRARY_VERSION "1.0.0") +set(SFS_LIBRARY_VERSION "1.1.0") project( sfsclient diff --git a/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h b/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h index a1a1b7e855..b2961f5c99 100644 --- a/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h +++ b/src/SfsClient/sfs-client/client/include/sfsclient/RequestParams.h @@ -35,6 +35,11 @@ struct RequestParams /// @note If not provided, a new CorrelationVector will be generated std::optional baseCV; + /// @brief Proxy setting which can be used to establish connections with the server (optional) + /// @note The string can be a hostname or dotted numerical IP address. It can be suffixed with the port number + /// like :[port], and can be prefixed with [scheme]://. If not provided, no proxy will be used. + std::optional proxy; + /// @brief Retry for a web request after a failed attempt. If true, client will retry up to c_maxRetries times bool retryOnError{true}; }; diff --git a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp b/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp index a1a1b10ef1..95d2abd8f5 100644 --- a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp +++ b/src/SfsClient/sfs-client/client/src/details/UrlBuilder.cpp @@ -66,6 +66,31 @@ std::string UrlBuilder::GetUrl() const return urlPtr; } +std::string UrlBuilder::GetPath() const +{ + CurlCharPtr path; + char* pathPtr = path.get(); + THROW_IF_CURL_URL_SETUP_ERROR(curl_url_get(m_handle, CURLUPART_PATH, &pathPtr, 0 /*flags*/)); + return pathPtr; +} + +std::string UrlBuilder::GetQuery() const +{ + CurlCharPtr query; + char* queryPtr = query.get(); + const auto queryResult = curl_url_get(m_handle, CURLUPART_QUERY, &queryPtr, 0 /*flags*/); + switch (queryResult) + { + case CURLUE_OK: + return queryPtr; + case CURLUE_NO_QUERY: + return {}; + default: + THROW_IF_CURL_URL_SETUP_ERROR(queryResult); + } + return {}; +} + UrlBuilder& UrlBuilder::SetScheme(Scheme scheme) { switch (scheme) diff --git a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h b/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h index f0b786defc..cf738dab1e 100644 --- a/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h +++ b/src/SfsClient/sfs-client/client/src/details/UrlBuilder.h @@ -39,6 +39,9 @@ class UrlBuilder std::string GetUrl() const; + std::string GetPath() const; + std::string GetQuery() const; + /** * @brief Set the scheme for the URL * @param scheme The scheme to set for the URL Ex: Https diff --git a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp index 7279ccf5aa..319d5a132c 100644 --- a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp +++ b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.cpp @@ -11,5 +11,6 @@ using namespace SFS::details; ConnectionConfig::ConnectionConfig(const SFS::RequestParams& requestParams) : maxRetries(requestParams.retryOnError ? c_maxRetries : 0) , baseCV(requestParams.baseCV) + , proxy(requestParams.proxy) { } diff --git a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h index 64fb42645b..30d963ae9f 100644 --- a/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h +++ b/src/SfsClient/sfs-client/client/src/details/connection/ConnectionConfig.h @@ -22,6 +22,9 @@ struct ConnectionConfig /// @brief The correlation vector to use for requests std::optional baseCV; + + /// @brief Proxy setting which can be used to establish connections with the server + std::optional proxy; }; } // namespace details } // namespace SFS diff --git a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp b/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp index 354dcab3b7..c7c0683141 100644 --- a/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp +++ b/src/SfsClient/sfs-client/client/src/details/connection/CurlConnection.cpp @@ -284,6 +284,11 @@ CurlConnection::CurlConnection(const ConnectionConfig& config, const ReportingHa m_handler, "Failed to set up curl"); + if (config.proxy) + { + THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_PROXY, config.proxy->c_str())); + } + // TODO #41: Pass AAD token in the header if it is available // TODO #42: Cert pinning with service } diff --git a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp b/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp index b13a4e0789..ce5680f166 100644 --- a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp +++ b/src/SfsClient/sfs-client/client/src/details/entity/ContentType.cpp @@ -3,16 +3,9 @@ #include "ContentType.h" -#include "../ErrorHandling.h" -#include "../ReportingHandler.h" -#include "Result.h" - -using namespace SFS; using namespace SFS::details; -namespace -{ -std::string ToString(ContentType type) +std::string SFS::details::ToString(ContentType type) { switch (type) { @@ -24,15 +17,3 @@ std::string ToString(ContentType type) return "Unknown"; } } -} // namespace - -void SFS::details::ValidateContentType(ContentType currentType, - ContentType expectedType, - const ReportingHandler& handler) -{ - THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType, - currentType != expectedType, - handler, - "Unexpected content type [" + ::ToString(currentType) + - "] returned by the service does not match the expected [" + ::ToString(expectedType) + "]"); -} diff --git a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h b/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h index 65c6e18250..2b9ccc4aa5 100644 --- a/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h +++ b/src/SfsClient/sfs-client/client/src/details/entity/ContentType.h @@ -3,15 +3,15 @@ #pragma once +#include + namespace SFS::details { -class ReportingHandler; - enum class ContentType { Generic, App, }; -void ValidateContentType(ContentType currentType, ContentType expectedType, const ReportingHandler& handler); +std::string ToString(ContentType type); } // namespace SFS::details diff --git a/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp b/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp index a427d793a9..53e8f114e7 100644 --- a/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp +++ b/src/SfsClient/sfs-client/client/src/details/entity/FileEntity.cpp @@ -66,6 +66,16 @@ Architecture ArchitectureFromString(const std::string& arch, const ReportingHand return Architecture::None; // Unreachable code, but the compiler doesn't know that. } } + +void ValidateContentType(const FileEntity& entity, ContentType expectedType, const ReportingHandler& handler) +{ + THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType, + entity.GetContentType() != expectedType, + handler, + "The service returned file \"" + entity.fileId + "\" with content type [" + + ToString(entity.GetContentType()) + "] while the expected type was [" + + ToString(expectedType) + "]"); +} } // namespace std::unique_ptr FileEntity::FromJson(const nlohmann::json& file, const ReportingHandler& handler) @@ -204,7 +214,7 @@ ContentType GenericFileEntity::GetContentType() const std::unique_ptr GenericFileEntity::ToFile(FileEntity&& entity, const ReportingHandler& handler) { - ValidateContentType(entity.GetContentType(), ContentType::Generic, handler); + ValidateContentType(entity, ContentType::Generic, handler); std::unordered_map hashes; for (auto& [hashType, hashValue] : entity.hashes) @@ -237,7 +247,7 @@ ContentType AppFileEntity::GetContentType() const std::unique_ptr AppFileEntity::ToAppFile(FileEntity&& entity, const ReportingHandler& handler) { - ValidateContentType(entity.GetContentType(), ContentType::App, handler); + ValidateContentType(entity, ContentType::App, handler); auto appEntity = dynamic_cast(entity); diff --git a/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp b/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp index f282fe1ab3..eacd847e88 100644 --- a/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp +++ b/src/SfsClient/sfs-client/client/src/details/entity/VersionEntity.cpp @@ -16,6 +16,19 @@ using namespace SFS; using namespace SFS::details; using json = nlohmann::json; +namespace +{ +void ValidateContentType(const VersionEntity& entity, ContentType expectedType, const ReportingHandler& handler) +{ + THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType, + entity.GetContentType() != expectedType, + handler, + "The service returned entity \"" + entity.contentId.name + "\" with content type [" + + ToString(entity.GetContentType()) + "] while the expected type was [" + + ToString(expectedType) + "]"); +} +} // namespace + std::unique_ptr VersionEntity::FromJson(const nlohmann::json& data, const ReportingHandler& handler) { // Expected format for a generic version entity: @@ -135,6 +148,6 @@ ContentType AppVersionEntity::GetContentType() const AppVersionEntity* AppVersionEntity::GetAppVersionEntityPtr(std::unique_ptr& versionEntity, const ReportingHandler& handler) { - ValidateContentType(versionEntity->GetContentType(), ContentType::App, handler); + ValidateContentType(*versionEntity, ContentType::App, handler); return dynamic_cast(versionEntity.get()); } diff --git a/src/SfsClient/sfs-client/client/tests/CMakeLists.txt b/src/SfsClient/sfs-client/client/tests/CMakeLists.txt index 91f4fe0a13..5782701e10 100644 --- a/src/SfsClient/sfs-client/client/tests/CMakeLists.txt +++ b/src/SfsClient/sfs-client/client/tests/CMakeLists.txt @@ -17,6 +17,8 @@ target_sources( functional/details/SFSClientImplTests.cpp functional/SFSClientTests.cpp mock/MockWebServer.cpp + mock/ProxyServer.cpp + mock/ServerCommon.cpp unit/AppContentTests.cpp unit/AppFileTests.cpp unit/ApplicabilityDetailsTests.cpp diff --git a/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp b/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp index 5ec016a88f..f5b90bc942 100644 --- a/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp +++ b/src/SfsClient/sfs-client/client/tests/functional/SFSClientTests.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "../mock/MockWebServer.h" +#include "../mock/ProxyServer.h" #include "../util/TestHelper.h" #include "TestOverride.h" #include "sfsclient/SFSClient.h" @@ -112,6 +113,19 @@ TEST("Testing SFSClient::GetLatestDownloadInfo()") CheckMockContent(contents[0], c_version); } + SECTION("No attributes + proxy") + { + test::ProxyServer proxy; + + params.productRequests = {{c_productName, {}}}; + params.proxy = proxy.GetBaseUrl(); + REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success); + REQUIRE(contents.size() == 1); + CheckMockContent(contents[0], c_version); + + REQUIRE(proxy.Stop() == Result::Success); + } + SECTION("With attributes") { const TargetingAttributes attributes{{"attr1", "value"}}; @@ -143,6 +157,8 @@ TEST("Testing SFSClient::GetLatestDownloadInfo()") CheckMockContent(contents[0], c_nextVersion); } } + + REQUIRE(server.Stop() == Result::Success); } TEST("Testing SFSClient::GetLatestAppDownloadInfo()") @@ -240,10 +256,13 @@ TEST("Testing SFSClient::GetLatestAppDownloadInfo()") params.productRequests = {{c_productName, {}}}; auto result = sfsClient->GetLatestAppDownloadInfo(params, contents); REQUIRE(result.GetCode() == Result::ServiceUnexpectedContentType); - REQUIRE(result.GetMsg() == - "Unexpected content type [Generic] returned by the service does not match the expected [App]"); + REQUIRE( + result.GetMsg() == + R"(The service returned entity "testProduct" with content type [Generic] while the expected type was [App])"); REQUIRE(contents.empty()); } + + REQUIRE(server.Stop() == Result::Success); } TEST("Testing SFSClient retry behavior") @@ -437,4 +456,6 @@ TEST("Testing SFSClient retry behavior") } } } + + REQUIRE(server.Stop() == Result::Success); } diff --git a/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp b/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp index 1d8a21ad68..77c114395a 100644 --- a/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp +++ b/src/SfsClient/sfs-client/client/tests/functional/details/CurlConnectionTests.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "../../mock/MockWebServer.h" +#include "../../mock/ProxyServer.h" #include "../../util/SFSExceptionMatcher.h" #include "../../util/TestHelper.h" #include "ReportingHandler.h" @@ -102,20 +103,46 @@ TEST("Testing CurlConnection()") { const std::string url = urlBuilder.GetSpecificVersionUrl(c_productName, c_version); - // Before registering the product, the URL returns 404 Not Found - REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); + json expectedResponse; + expectedResponse["ContentId"] = {{"Namespace", "default"}, {"Name", c_productName}, {"Version", c_version}}; - // Register the product - server.RegisterProduct(c_productName, c_version); + SECTION("Direct connection") + { + // Before registering the product, the URL returns 404 Not Found + REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); - // After registering the product, the URL returns 200 OK - std::string out; - REQUIRE_NOTHROW(out = connection->Get(url)); + // Register the product + server.RegisterProduct(c_productName, c_version); - json expectedResponse; - expectedResponse["ContentId"] = {{"Namespace", "default"}, {"Name", c_productName}, {"Version", c_version}}; + // After registering the product, the URL returns 200 OK + std::string out; + REQUIRE_NOTHROW(out = connection->Get(url)); + + REQUIRE(json::parse(out) == expectedResponse); + } + + SECTION("With proxy") + { + test::ProxyServer proxy; + + ConnectionConfig config; + config.proxy = proxy.GetBaseUrl(); + connection = connectionManager.MakeConnection(config); + + // Before registering the product, the URL returns 404 Not Found + REQUIRE_THROWS_CODE(connection->Get(url), HttpNotFound); + + // Register the product + server.RegisterProduct(c_productName, c_version); + + // After registering the product, the URL returns 200 OK + std::string out; + REQUIRE_NOTHROW(out = connection->Get(url)); - REQUIRE(json::parse(out) == expectedResponse); + REQUIRE(json::parse(out) == expectedResponse); + + REQUIRE(proxy.Stop() == Result::Success); + } } SECTION("Testing CurlConnection::Post()") @@ -153,6 +180,21 @@ TEST("Testing CurlConnection()") expectedResponse.push_back( {{"ContentId", {{"Namespace", "default"}, {"Name", c_productName}, {"Version", c_nextVersion}}}}); REQUIRE(json::parse(out) == expectedResponse); + + SECTION("Testing with proxy") + { + test::ProxyServer proxy; + + ConnectionConfig config; + config.proxy = proxy.GetBaseUrl(); + connection = connectionManager.MakeConnection(config); + + REQUIRE_NOTHROW(out = connection->Post(url, body.dump())); + + REQUIRE(json::parse(out) == expectedResponse); + + REQUIRE(proxy.Stop() == Result::Success); + } } SECTION("With GetDownloadInfo mock") @@ -187,6 +229,21 @@ TEST("Testing CurlConnection()") {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/2.bin"}, {"HashOfHashes", "abcd"}}}}; REQUIRE(json::parse(out) == expectedResponse); + + SECTION("Testing with proxy") + { + test::ProxyServer proxy; + + ConnectionConfig config; + config.proxy = proxy.GetBaseUrl(); + connection = connectionManager.MakeConnection(config); + + REQUIRE_NOTHROW(out = connection->Post(url)); + + REQUIRE(json::parse(out) == expectedResponse); + + REQUIRE(proxy.Stop() == Result::Success); + } } } diff --git a/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp b/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp index b077dbdc16..be085ac05d 100644 --- a/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp +++ b/src/SfsClient/sfs-client/client/tests/mock/MockWebServer.cpp @@ -5,6 +5,7 @@ #include "../util/TestHelper.h" #include "ErrorHandling.h" +#include "ServerCommon.h" #include "Util.h" #include "connection/HttpHeader.h" @@ -13,7 +14,6 @@ #include #endif -#include #include #include @@ -27,46 +27,10 @@ using namespace SFS::details; using namespace SFS::details::util; using namespace SFS::test; using namespace SFS::test::details; -using namespace std::string_literals; using json = nlohmann::json; -#define BUILD_BUFFERED_LOG_DATA(message) \ - BufferedLogData \ - { \ - message, __FILE__, __LINE__, __FUNCTION__, std::chrono::system_clock::now() \ - } - -#define BUFFER_LOG(message) BufferLog(BUILD_BUFFERED_LOG_DATA(message)) - -const char* c_listenHostName = "localhost"; - namespace { -std::string ToString(httplib::StatusCode status) -{ - return std::to_string(status) + " " + std::string(httplib::status_message(status)); -} - -class StatusCodeException : public std::exception -{ - public: - StatusCodeException(httplib::StatusCode status) : m_status(status) - { - } - - const char* what() const noexcept override - { - return ToString(m_status).c_str(); - } - - httplib::StatusCode GetStatusCode() const - { - return m_status; - } - - private: - httplib::StatusCode m_status; -}; struct App { @@ -230,20 +194,6 @@ json GeneratePostAppDownloadInfo(const std::string& name) return response; } -struct BufferedLogData -{ - std::string message; - std::string file; - unsigned line; - std::string function; - std::chrono::time_point time; -}; - -LogData ToLogData(const BufferedLogData& data) -{ - return {LogSeverity::Info, data.message.c_str(), data.file.c_str(), data.line, data.function.c_str(), data.time}; -} - void CheckApiVersion(const httplib::Request& req, std::string_view apiVersion) { if (util::AreNotEqualI(req.path_params.at("apiVersion"), apiVersion)) @@ -255,7 +205,7 @@ void CheckApiVersion(const httplib::Request& req, std::string_view apiVersion) namespace SFS::test::details { -class MockWebServerImpl +class MockWebServerImpl : public BaseServerImpl { public: MockWebServerImpl() = default; @@ -264,11 +214,6 @@ class MockWebServerImpl MockWebServerImpl(const MockWebServerImpl&) = delete; MockWebServerImpl& operator=(const MockWebServerImpl&) = delete; - void Start(); - Result Stop(); - - std::string GetUrl() const; - void RegisterProduct(std::string&& name, std::string&& version); void RegisterAppProduct(std::string&& name, std::string&& version, std::vector&& prerequisites); void RegisterExpectedRequestHeader(std::string&& header, std::string&& value); @@ -276,8 +221,8 @@ class MockWebServerImpl void SetResponseHeaders(std::unordered_map headersByCode); private: - void ConfigureServerSettings(); - void ConfigureRequestHandlers(); + void ConfigureRequestHandlers() override; + std::string GetLogIdentifier() override; void ConfigurePostLatestVersion(); void ConfigurePostLatestVersionBatch(); @@ -291,16 +236,6 @@ class MockWebServerImpl const std::function& callback); void CheckRequestHeaders(const httplib::Request& req); - void BufferLog(const BufferedLogData& data); - void ProcessBufferedLogs(); - - httplib::Server m_server; - int m_port{-1}; - - std::optional m_lastException; - - std::thread m_listenerThread; - using VersionList = std::set; std::unordered_map m_products; @@ -310,9 +245,6 @@ class MockWebServerImpl std::unordered_map m_expectedRequestHeaders; std::queue m_forcedHttpErrors; std::unordered_map m_headersByCode; - - std::vector m_bufferedLog; - std::mutex m_logMutex; }; } // namespace SFS::test::details @@ -369,49 +301,6 @@ void MockWebServer::SetResponseHeaders(std::unordered_map h m_impl->SetResponseHeaders(std::move(headersByCode)); } -void MockWebServerImpl::Start() -{ - ConfigureServerSettings(); - ConfigureRequestHandlers(); - - m_port = m_server.bind_to_any_port(c_listenHostName); - m_listenerThread = std::thread([&]() { m_server.listen_after_bind(); }); -} - -void MockWebServerImpl::ConfigureServerSettings() -{ - m_server.set_logger([&](const httplib::Request& req, const httplib::Response& res) { - BUFFER_LOG("Request: " + req.method + " " + req.path + " " + req.version); - BUFFER_LOG("Request Body: " + req.body); - - BUFFER_LOG("Response: " + res.version + " " + ::ToString(static_cast(res.status)) + " " + - res.reason); - BUFFER_LOG("Response body: " + res.body); - }); - - m_server.set_exception_handler([&](const httplib::Request&, httplib::Response& res, std::exception_ptr ep) { - try - { - std::rethrow_exception(ep); - } - catch (std::exception& e) - { - m_lastException = Result(Result::HttpUnexpected, e.what()); - } - catch (...) - { - m_lastException = Result(Result::HttpUnexpected, "Unknown Exception"); - } - - ProcessBufferedLogs(); - - res.status = httplib::StatusCode::InternalServerError_500; - }); - - // Keeping this interval to a minimum ensures tests run quicker - m_server.set_keep_alive_timeout(1); // 1 second -} - void MockWebServerImpl::ConfigureRequestHandlers() { ConfigurePostLatestVersion(); @@ -420,6 +309,11 @@ void MockWebServerImpl::ConfigureRequestHandlers() ConfigurePostDownloadInfo(); } +std::string MockWebServerImpl::GetLogIdentifier() +{ + return "MockWebServer"; +} + void MockWebServerImpl::ConfigurePostLatestVersion() { // Path: /api//contents//namespaces//names//versions/latest?action=select @@ -764,37 +658,6 @@ void MockWebServerImpl::CheckRequestHeaders(const httplib::Request& req) } } -void MockWebServerImpl::BufferLog(const BufferedLogData& data) -{ - std::lock_guard guard(m_logMutex); - m_bufferedLog.push_back(data); -} - -void MockWebServerImpl::ProcessBufferedLogs() -{ - for (const auto& data : m_bufferedLog) - { - LogCallbackToTest(ToLogData(data)); - } - m_bufferedLog.clear(); -} - -Result MockWebServerImpl::Stop() -{ - if (m_listenerThread.joinable()) - { - m_server.stop(); - m_listenerThread.join(); - } - ProcessBufferedLogs(); - return m_lastException.value_or(Result::Success); -} - -std::string MockWebServerImpl::GetUrl() const -{ - return "http://"s + c_listenHostName + ":"s + std::to_string(m_port); -} - void MockWebServerImpl::RegisterProduct(std::string&& name, std::string&& version) { m_products[std::move(name)].emplace(std::move(version)); diff --git a/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp b/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp new file mode 100644 index 0000000000..2ee330a338 --- /dev/null +++ b/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.cpp @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "ProxyServer.h" + +#include "../util/TestHelper.h" +#include "ErrorHandling.h" +#include "ReportingHandler.h" +#include "ServerCommon.h" +#include "UrlBuilder.h" + +using namespace SFS; +using namespace SFS::details; +using namespace SFS::test; +using namespace SFS::test::details; + +namespace SFS::test::details +{ +class ProxyServerImpl : public BaseServerImpl +{ + private: + void ConfigureRequestHandlers() override; + std::string GetLogIdentifier() override; +}; +} // namespace SFS::test::details + +ProxyServer::ProxyServer() +{ + m_impl = std::make_unique(); + m_impl->Start(); +} + +ProxyServer::~ProxyServer() +{ + const auto ret = Stop(); + if (!ret) + { + TEST_UNSCOPED_INFO("Failed to stop: " + std::string(ToString(ret.GetCode()))); + } +} + +Result ProxyServer::Stop() +{ + return m_impl->Stop(); +} + +std::string ProxyServer::GetBaseUrl() const +{ + return m_impl->GetUrl(); +} + +void ProxyServerImpl::ConfigureRequestHandlers() +{ + auto HandleRequest = [&](const httplib::Request& req, httplib::Response& res) { + // As a proxy, we'll parse the URL and Path/Query so we can reuse them in httplib::Client + ReportingHandler handler; + UrlBuilder urlBuilder(req.target.c_str(), handler); + const std::string path = urlBuilder.GetPath(); + const std::string query = urlBuilder.GetQuery(); + urlBuilder.ResetPath().ResetQuery(); + + // URL may come back from UrlBuilder with a final /, which doesn't work with httplib::Client, so we remove it + auto url = urlBuilder.GetUrl(); + if (url.at(url.size() - 1) == '/') + { + url.pop_back(); + } + const std::string pathAndQuery = path + (query.empty() ? "" : ("?" + query)); + httplib::Client cli(url); + httplib::Result result; + if (req.method == "GET") + { + result = cli.Get(pathAndQuery, req.headers); + } + else if (req.method == "POST") + { + const auto length = std::stoi(req.get_header_value("Content-Length")); + result = + cli.Post(pathAndQuery, req.headers, req.body.c_str(), length, req.get_header_value("Content-Type")); + } + + if (!result) + { + BUFFER_LOG("Client error: " + to_string(result.error())); + throw StatusCodeException(httplib::StatusCode::InternalServerError_500); + } + res = result.value(); + }; + + m_server.Get(".*", HandleRequest); + m_server.Post(".*", HandleRequest); +} + +std::string ProxyServerImpl::GetLogIdentifier() +{ + return "ProxyServer"; +} diff --git a/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h b/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h new file mode 100644 index 0000000000..eb833b2985 --- /dev/null +++ b/src/SfsClient/sfs-client/client/tests/mock/ProxyServer.h @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "Result.h" + +#include + +namespace SFS::test +{ +namespace details +{ +class ProxyServerImpl; +} + +// Proxy Server implementation that redirects GET and POST requests directly +class ProxyServer +{ + public: + ProxyServer(); + ~ProxyServer(); + + ProxyServer(const ProxyServer&) = delete; + ProxyServer& operator=(const ProxyServer&) = delete; + + Result Stop(); + + std::string GetBaseUrl() const; + + private: + std::unique_ptr m_impl; +}; +} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp b/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp new file mode 100644 index 0000000000..bdf3bb2d3d --- /dev/null +++ b/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.cpp @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "ServerCommon.h" + +#include "../util/TestHelper.h" +#include "Result.h" + +using SFS::test::BufferedLogData; +using SFS::test::StatusCodeException; +using SFS::test::details::BaseServerImpl; +using namespace SFS; +using namespace std::string_literals; + +static constexpr const char* c_listenHostName = "localhost"; + +static std::string ToString(httplib::StatusCode status) +{ + return std::to_string(status) + " " + std::string(httplib::status_message(status)); +} + +StatusCodeException::StatusCodeException(httplib::StatusCode status) : m_status(status), m_message(::ToString(m_status)) +{ +} + +const char* StatusCodeException::what() const noexcept +{ + return m_message.c_str(); +} + +httplib::StatusCode StatusCodeException::GetStatusCode() const +{ + return m_status; +} + +static SFS::LogData ToLogData(const BufferedLogData& data) +{ + return {LogSeverity::Info, data.message.c_str(), data.file.c_str(), data.line, data.function.c_str(), data.time}; +} + +void BaseServerImpl::Start() +{ + ConfigureServerSettings(); + ConfigureRequestHandlers(); + + m_port = m_server.bind_to_any_port(c_listenHostName); + m_listenerThread = std::thread([&]() { m_server.listen_after_bind(); }); +} + +void BaseServerImpl::ConfigureServerSettings() +{ + m_server.set_logger([&](const httplib::Request& req, const httplib::Response& res) { + BUFFER_LOG("Request: " + req.method + " " + req.path + " " + req.version); + BUFFER_LOG("Request Body: " + req.body); + + BUFFER_LOG("Response: " + res.version + " " + ::ToString(static_cast(res.status)) + " " + + res.reason); + BUFFER_LOG("Response body: " + res.body); + }); + + m_server.set_exception_handler([&](const httplib::Request&, httplib::Response& res, std::exception_ptr ep) { + try + { + std::rethrow_exception(ep); + } + catch (std::exception& e) + { + m_lastException = Result(Result::HttpUnexpected, e.what()); + } + catch (...) + { + m_lastException = Result(Result::HttpUnexpected, "Unknown Exception"); + } + + ProcessBufferedLogs(); + + res.status = httplib::StatusCode::InternalServerError_500; + }); + + // Keeping this interval to a minimum ensures tests run quicker + m_server.set_keep_alive_timeout(1); // 1 second +} + +void BaseServerImpl::BufferLog(const BufferedLogData& data) +{ + std::lock_guard guard(m_logMutex); + m_bufferedLog.push_back(data); +} + +BufferedLogData BaseServerImpl::BuildBufferedLogData(const std::string& message, + const char* file, + unsigned line, + const char* function) +{ + return BufferedLogData{GetLogIdentifier() + ": " + message, file, line, function, std::chrono::system_clock::now()}; +} + +void BaseServerImpl::ProcessBufferedLogs() +{ + for (const auto& data : m_bufferedLog) + { + LogCallbackToTest(ToLogData(data)); + } + m_bufferedLog.clear(); +} + +Result BaseServerImpl::Stop() +{ + if (m_listenerThread.joinable()) + { + m_server.stop(); + m_listenerThread.join(); + } + ProcessBufferedLogs(); + return m_lastException.value_or(Result::Success); +} + +std::string BaseServerImpl::GetUrl() const +{ + return "http://"s + c_listenHostName + ":"s + std::to_string(m_port); +} diff --git a/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h b/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h new file mode 100644 index 0000000000..bd3cca9fcd --- /dev/null +++ b/src/SfsClient/sfs-client/client/tests/mock/ServerCommon.h @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "Logging.h" +#include "Result.h" + +#include + +#include +#include +#include + +namespace SFS::test +{ +class StatusCodeException : public std::exception +{ + public: + StatusCodeException(httplib::StatusCode status); + + const char* what() const noexcept override; + + httplib::StatusCode GetStatusCode() const; + + private: + httplib::StatusCode m_status; + std::string m_message; +}; + +#define BUILD_BUFFERED_LOG_DATA(message) BuildBufferedLogData(message, __FILE__, __LINE__, __FUNCTION__) + +#define BUFFER_LOG(message) BufferLog(BUILD_BUFFERED_LOG_DATA(message)) + +struct BufferedLogData +{ + std::string message; + std::string file; + unsigned line; + std::string function; + std::chrono::time_point time; +}; + +namespace details +{ +class BaseServerImpl +{ + public: + BaseServerImpl() = default; + ~BaseServerImpl() = default; + + BaseServerImpl(const BaseServerImpl&) = delete; + BaseServerImpl& operator=(const BaseServerImpl&) = delete; + + void Start(); + Result Stop(); + + std::string GetUrl() const; + + protected: + void ConfigureServerSettings(); + virtual void ConfigureRequestHandlers() = 0; + + virtual std::string GetLogIdentifier() = 0; + + void BufferLog(const BufferedLogData& data); + BufferedLogData BuildBufferedLogData(const std::string& message, + const char* file, + unsigned line, + const char* function); + void ProcessBufferedLogs(); + + httplib::Server m_server; + int m_port{-1}; + + std::optional m_lastException; + + std::thread m_listenerThread; + + std::vector m_bufferedLog; + std::mutex m_logMutex; +}; +} // namespace details +} // namespace SFS::test diff --git a/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp b/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp index 496482b97e..33cc0d5cc3 100644 --- a/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp +++ b/src/SfsClient/sfs-client/client/tests/unit/SFSClientTests.cpp @@ -261,6 +261,44 @@ void TestProductInRequestParams(const std::function .\scripts\Setup.ps1 #> -param ( - [switch] $NoBuildTools -) $ErrorActionPreference = "Stop" @@ -71,19 +68,33 @@ function Install-CMake { } function Install-CppBuildTools { - if ($NoBuildTools) { - Write-Host -ForegroundColor Yellow "`nSkipping C++ Build Tools installation" - return - } + Write-Host -ForegroundColor Cyan "`nInstalling C++ Builds tools if they are not installed" - Write-Host -ForegroundColor Cyan "`nInstalling C++ Build Tools" + # Instaling vswhere, which will be used to query for the required build tools + try { + vswhere -? 2>&1 | Out-Null + } + catch { + winget install vswhere + if (!$?) { + Write-Host -ForegroundColor Red "Failed to install vswhere" + exit 1 + } + } # - Microsoft.VisualStudio.Workload.VCTools is the C++ workload in the Visual Studio Build Tools - # --wait makes the install synchronous - winget install Microsoft.VisualStudio.2022.BuildTools --silent --override "--wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --remove Microsoft.VisualStudio.Component.VC.CMake.Project" - if (!$?) { - Write-Host -ForegroundColor Red "Failed to install build tools" - exit 1 + # - Microsoft.VisualStudio.Workload.NativeDesktop is the C++ workload that comes pre-installed in the github runner image + $ExistingBuildTools = vswhere -products * -requires Microsoft.VisualStudio.Workload.VCTools Microsoft.VisualStudio.Workload.NativeDesktop -requiresAny -format json | ConvertFrom-Json + if ($null -eq $ExistingBuildTools) + { + Write-Host "`nTools not found, installing..." + + # --wait makes the install synchronous + winget install Microsoft.VisualStudio.2022.BuildTools --silent --override "--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --remove Microsoft.VisualStudio.Component.VC.CMake.Project" + if (!$?) { + Write-Host -ForegroundColor Red "Failed to install build tools" + exit 1 + } } } @@ -93,13 +104,15 @@ function Install-Vcpkg { if (!(Test-Path vcpkg)) { Write-Host "Cloning vcpkg repo" git clone https://github.com/microsoft/vcpkg $GitRoot\vcpkg - & "$GitRoot\vcpkg\bootstrap-vcpkg.bat" } else { Write-Host "Checking if vcpkg repo has new commits" $NoUpdatesString = "Already up to date." git -C "$GitRoot\vcpkg" pull --show-forced-updates | Select-String -Pattern $NoUpdatesString -NotMatch } + + # Bootstrapping on every setup updates the vcpkg.exe and solves potential issues with VS Build Tools not being found + & "$GitRoot\vcpkg\bootstrap-vcpkg.bat" } function Set-GitHooks { diff --git a/src/SfsClient/sfs-client/scripts/pip.requirements.txt b/src/SfsClient/sfs-client/scripts/pip.requirements.txt index 377f1e824f..bae1499f62 100644 --- a/src/SfsClient/sfs-client/scripts/pip.requirements.txt +++ b/src/SfsClient/sfs-client/scripts/pip.requirements.txt @@ -1,2 +1,2 @@ -clang-format==18.1.5 +clang-format==19.1.1 cmake-format==0.6.13 \ No newline at end of file diff --git a/src/WinGetSourceCreator/WinGetSourceCreator.csproj b/src/WinGetSourceCreator/WinGetSourceCreator.csproj index c5fa565e94..38aa96151e 100644 --- a/src/WinGetSourceCreator/WinGetSourceCreator.csproj +++ b/src/WinGetSourceCreator/WinGetSourceCreator.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net8.0 $(SolutionDir)$(Platform)\$(Configuration)\WinGetSourceCreator\ enable enable @@ -9,6 +9,8 @@ + + diff --git a/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj b/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj index 618c708bc4..5b8e0d0784 100644 --- a/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj +++ b/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 x64;x86 @@ -12,6 +12,8 @@ + + diff --git a/src/vcpkg.json b/src/vcpkg.json index 677d3b2388..e7d34a4f5d 100644 --- a/src/vcpkg.json +++ b/src/vcpkg.json @@ -22,7 +22,7 @@ }, { "name": "curl", - "version": "8.10.1" + "version": "8.11.0" }, { "name": "nlohmann-json", @@ -38,5 +38,5 @@ "./VcpkgCustomTriplets" ] }, - "builtin-baseline": "f176b58f35a75f9f8f54099cd9df97d2e2793a2e" + "builtin-baseline": "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c" }